Šablony jsou základem obecného programování v jazyce C++. Jako jazyk se silným typem vyžaduje jazyk C++, aby všechny proměnné měly určitý typ, a to buď explicitně deklarovaný programátorem, nebo vyvolaný kompilátorem. Mnoho datových struktur a algoritmů ale vypadá stejně bez ohledu na to, na jakém typu pracují. Šablony umožňují definovat operace třídy nebo funkce a umožnit uživateli určit konkrétní typy těchto operací.
#ifndef ODMENA_HPP_INCLUDED #define ODMENA_HPP_INCLUDED ... #endif
// odmena.cpp Odmena odmenaPro(const Osoba& zaslouzilec) { if (zaslouzilec.povolani == Povolani::cukrar) return Odmena::chlebicky; return Odmena::cokolada; }
// odmena.hpp struct Osoba; enum class Odmena { chlebicky, cokolada }; Odmena odmenaPro(const Osoba& zaslouzilec);
inline
je povoleno umístit do hlavičkových souborů.
// util.hpp inline int maximum(int a, int b) { return (a > b) ? a : b; }
inline
. Proto nevadí, když jsou umístěny v hlavičkových souborech.
// osoba.hpp struct Osoba { int kolikTiJe() { if (pohl == Pohlavi::zena) return (4 * vek) / 5; else return vek; } ... };
V dnešním cvičení si hlavně budeme procvičovat psaní šablon. O tom, co šablony jsou a jak fungují, se můžete dozvědět v přednáškové prezentaci nebo na této stránce. Začneme u našeho dobrého známého – vektoru. Pro účely tohoto cvičení je redukovaný a některé metody, které jsme již jednou implementovali v něm nejsou.
array.cpp
copy_array
, print_array
, resize_array
šablonami funkcí tak, aby funkce přijímaly libovolný typ pole.
Nezapomeňte, že šablony musí být umístěny v hlavičkovém souboru, jinak zpravidla
dojde k chybě. Přesuňte tedy všechny funkce do souboru array.hpp
.
Nyní máme k dispozici všechny nástroje k tomu, abychom mohli konečně vytvořit
šablonu třídy vector
, která přijme libovolný typ.
vector
šablonou třídy vector
.
Chtěli bychom, aby fungoval následující kód:
#include "vector.hpp" #include <string> #include <iostream> #include <algorithm> int main() { vector<double> v; v.push_back(4.56); v.push_back(7.89); v.push_back(1.23); std::sort(v.begin(), v.end()); std::cout << v << '\n'; vector<std::string> u; u.push_back("def"); u.push_back("ghi"); u.push_back("abc"); std::sort(u.begin(), u.end()); std::cout << u << '\n'; }
Nyní máme (redukovaný) šablonový vektor, ale rádi bychom přidali ještě jednu možnost použití, kdy se do vektoru najednou přidá více prvků z jiné kolekce nebo range. Zkrátka bychom chtěli, aby tento kód též fungoval:
#include "vector.hpp" #include <iostream> #include <algorithm> #include <vector> int main() { std::vector<double> vec {1.21, 3.42, 5.123, 4.232, 9.9}; vector<double> v; v.push_back(4.56); v.push_back(7.89); v.push_back(1.23); v.push_back(begin(vec), end(vec)); // Přidá nakonec všechny prvky z vec // v.push_back(std::istream_iterator<double>(std::cin), std::istream_iterator<double>()); -- takto se dají načítat čísla přímo ze stdin std::sort(v.begin(), v.end()); std::cout << v << '\n'; }Základní implementace je jednoduchá:
template <typename T> class vector { public: ... template <typename InputIterator> void push_back(InputIterator from, InputIterator to) { while (from != to) { push_back(*from); ++from; } }
Náš vektor nyní umí přidat více prvků najednou, ale implementace šablonové
metody vector::push_back(InputIterator from, InputIterator to)
je velmi
primitivní a šla by zlepšit. Například bychom mohli zaručit, že dojde maximálně
k jedné alokaci během jednoho volání range push_back
, pokud si můžeme předem
spočítat kolik nových prvků musíme vložit1).
Protože to ale nejde udělat se všemi iterátory, musíme použit metaprogramování, aby se během kompilace zavolala správná metoda.
private
metody push_back
, které slouží k implementaci veřejné metody push_back
pro různé iterátory (Nezapomeňte: pro InputIterator nemůžeme posunout iterátor, aniž bychom navždy zahodili data na která ukazuje).
push_back
musíme rozhodovat pro optimální chování? Šlo by to zjednodušit?
Vyzkoušíme si úpravu funkcionality algoritmu std::sort
. Jako třetí parametr
tato šablonová funkce očekává komparátor; komparátor má za úkol pro dané dva
prvky rozhodnout, zda první je menší než druhý. Predikát může mít několik podob:
std::sort
ukazatel na takovou funkci, aby pole bylo seřazené sestupně.
std::sort
takový funkční objekt, aby pole bylo seřazené sestupně.
std::sort
takovou lambda funkci, aby pole bylo seřazené sestupně.
std::sort
instanci šablony std::greater
z hlavičky <functional>
.
Jak docílí std::sort
toho, aby mohl příjímat tak široký výběr komparátorů?
Ukážeme si, jak to udělat u vlastních funkcí.
is_sorted
, která ověří, zda je kolekce typu vector<double>
seřazená. Kolekci předávajte konstantní referencí.
Container
.
Iterator
, šablonový parametr Container
už nebude potřeba. Použití dvou iterátorů namísto celé kolekce má tu výhodu, že je obecnější - můžeme například ověřit seřazenost pouze části pole.
is_sorted
, které navíc získá komparátor jako parametr. Typ komparátoru bude určen dalším šablonovým parametrem, Comparator
.
lazy_sort
, která pro dva iterátory nejdříve ověří, zda je pole seřazené pomocí is_sorted
. Pokud prvky mezi komparátory seřazené nejsou, lazy_sort
prvky seřadí pomocí std::sort
. Vytvořte lazy_sort
s komparátorem a bez komparátoru.