Šablona (C++)
Šablona (anglicky template) je nástroj typové parametrizace v programovacím jazyce C++. Šablony umožňují generické programování a typově bezpečné kontejnery.
Ve standardní knihovně C++ se šablony používají pro poskytování typově bezpečných kontejnerů, např. seznamů, a pro implementaci generických algoritmů, jako jsou např. řadicí algoritmy. Šablony v C++ jsou do značné míry inspirovány parametrizovatelnými moduly v jazyce CLU a generiky v jazyce Ada.[1]
V jiných programovacích jazycích (např. v Javě[2] nebo C#[3]) existuje koncept generických typů, které se šablonami souvisejí. Generické typy však nejsou generátory kódu, ale pouze umožňují použití typově bezpečných kontejnerů a algoritmů.
Druhy šablon
[editovat | editovat zdroj]V jazyce C++ existují tři druhy šablon: šablony funkcí, třídní šablony a šablony proměnných:
Šablony funkcí
[editovat | editovat zdroj]Šablony funkcí, šablonové funkce, funkční šablony se chovají jako funkce, které přijímají argumenty různých typů nebo vracejí různé typy návratových hodnot. Standardní knihovna C++ obsahuje například funkční šablonu std::max(x, y)
, která vrací větší z argumentů. Mohla by být definována následujícím způsobem:
template <typename T>
T max(T x, T y) {
T value;
if (x < y)
value = y;
else
value = x;
return value;
}
Tuto šablonu lze volat stejně, jako kdyby to byla funkce:
cout << max(3, 7); // vypíše 7
Podle argumentů kompilátor rozhodne, že se jedná o volání funkce max(int, int), a vygeneruje variantu funkce, ve které je generický typ T
nastaven na int
.
Parametr šablony může být také zadán explicitně:
cout << max<int>(3, 7); // také vypíše 7
Šablonu funkce max()
lze instanciovat pro libovolný typ, pro který je definováno porovnání x < y
. U typů definovaných programátorem lze významu operátoru <
pro tento typ definovat jeho přetížením. Tím se umožní také použití funkce max()
pro tento typ.
V kombinaci se standardní knihovnou C++ se definováním několika operátorů otevírá programátorovi obrovská funkčnost pro typy, které si sám nadefinoval. Stačí definovat operátor porovnávání <
(ostrá nerovnost), a hned je možné pro vlastní typ používat standardní algoritmy std::sort()
, std::stable_sort()
a std::binary_search()
.
Šablony tříd
[editovat | editovat zdroj]Šablony tříd, třídní šablony uplatňují stejný přístup k třídám. Šablony tříd se často používají k vytváření obecných kontejnerů. Standardní knihovna C++ definuje například kontejner std::list
, který implementuje spojový seznam. Pro vytvoření spojového seznamu hodnot typu int
se použije zápis typu std::list<int>
. Pro spojový seznam řetězců typu std::string
se použij zápis typu std::list<std::string>
. Pro seznamy je definována sada standardních funkcí, které jsou k dispozici vždy, bez ohledu na to, jaký typ argumentu je uveden v lomených závorkách. Hodnoty v lomených závorkách se nazývají parametry. Pokud je kompilátoru předána šablona třídy s jejími parametry, může kompilátor šablonu charakterizovat. Pro každý typ parametru vytvoří samostatnou třídu šablony. Jedná se o obyčejnou třídu, stejně jako jakákoli jiná. Pojmy šablona třídy a třída šablony je zde třeba od sebe odlišovat. Jde o podobný vztah jako vztah objektu a třídy; třída šablony je konkrétní realizace šablony třídy.
Šablony lze použít jak pro třídy definované pomocí class
, tak pro třídy definované pomocí struct
a union
. Naproti tomu jmenné prostory nelze vytvářet jako šablony. V C++11 byla přidána možnost vytvářet definice typů jako šablony pomocí typedef
.
Dědění
[editovat | editovat zdroj]Stejně jako běžné třídy se i šablony tříd mohou objevit v hierarchiích dědičnosti jako základní i odvozené třídy.
Pokud je šablona třídy definována s různými parametry třídy, nejsou tyto obecně ve vztahu dědičnosti – a to ani v případě, že parametry šablony jsou ve vztahu dědičnosti.
- Příklad
class Zakladni {...};
class Odvozena: Zakladni { ... };
Zakladni* b = new Odvozena; // OK. Automatická konverze typu, protože jde o základní třídu.
std::vector<Zakladni>* vb = new std::vector<Odvozena>; // CHYBA!
Existují dva hlavní důvody, proč takové konverze nejsou povoleny:
Za prvé jsou to technické důvody: std::vector<T>
ukládá své prvky do pole na přímo po sobě jdoucí adresy. Pokud má objekt typu Odvozena
jinou velikost než objekt typu Zakladni
, pak rozložení paměti std::vector<Zakladni>
již neodpovídá rozložení std::vector<Odvozena>
a přístup k prvkům by selhal.
Za druhé pro to existují i logické důvody: Kontejner, který obsahuje prvky základní třídy, odkazuje na prvky, jejichž datový typ je Zakladni
nebo jsou od Zakladni
odvozené. Je tedy výkonnější než kontejner, který může obsahovat pouze prvky typu Odvozena
, ale méně výkonný, protože zaručuje vrácení pouze prvků typu Zakladni
. U std::vector<T>
to nejjednodušeji ilustruje operátor []
.
- Příklad
class Zakladni {...};
class Odvozena: Zakladni { ... };
Zakladni* b = new Zakladni;
Odvozena* d = new Odvozena;
std::vector<Zakladni>* vb = new std::vector<Zakladni>;
std::vector<Odvozena>* vd = new std::vector<Odvozena>;
Na jedné straně je možné použít vb[0] = b
, ale ne vd[0] = b
. Na druhé straně nelze použít d = vd[0]
, ale je možné d = vb[0]
.
Pokud má smysl konverze různých verzí téže šablony třídy – například u std::shared_ptr
– musí být pro tento účel explicitně definován operátor typové konverze.
Šablony tříd nelze přetěžovat jako šablony funkcí. Lze ale vytvářet specializace šablon tříd.
Šablony proměnných
[editovat | editovat zdroj]Od verze C++14 je také možné definovat šablony proměnných. To umožňuje, aby proměnné, které logicky patří k sobě nebo jsou „stejné“ až na typ, byly odpovídajícím způsobem označeny:
// Šablona proměnné
template<class T>
constexpr T Pi = T(3.1415926535897932385L);
// Šablona funkce
template<class T>
T plocha_kruhu(T r) {
return Pi<T> * r * r;
}
Funkci plocha_kruhu()
lze nyní instanciovat pro různé aritmetické datové typy a vždy přistupuje ke konstantě Pi
, jejíž typ odpovídá typu parametru.
Šablonové aliasy
[editovat | editovat zdroj]C++11 představil šablonové aliasy, které fungují jako parametrizovatelné definice typů pomocí typedef.
Následující kód ukazuje definici šablonového aliasu StrMap
, který například umožňuje použít StrMap<int>
jako zkratku za std::unordered_map<int,std::string>
.
template<typename T> using StrMap = std::unordered_map<T, std::string>;
Specializace
[editovat | editovat zdroj]Šablony mohou být specializované, tj. šablony tříd a funkcí (pro určité datové typy jako argumenty šablony) mohou být implementovány rozdílně. To umožňuje efektivnější implementaci pro určité vybrané datové typy, aniž by se měnilo rozhraní šablony. Toho využívá i mnoho implementací standardní knihovny C++ (například GCC).
Specializace šablon tříd
[editovat | editovat zdroj]Kontejnerovou třídu std::vector
standardní knihovny C++ lze pro prvky typu bool implementovat jako bitovou mapu, aby se šetřila paměť. Šablona třídy std::basic_string
také přebírá informace pro práci s jednotlivými symboly ze struktury char_traits, která je specializovaná pro datový typ char a také například pro wchar_t.
Deklarace specializace se podobá deklaraci normálních šablon. Lomené závorky za klíčovým slovem template
jsou však prázdné a za názvem funkce nebo třídy následují parametry šablony.
- příklad
template<>
class vector<bool> {
// implementace vektoru jako bitové mapy
};
Částečná specializace
[editovat | editovat zdroj]Existuje také takzvaná částečná specializace, které umožňuje zvláštní zacházení ve speciálních případech.
- příklad
template<int radky, int sloupce>
class Matice {
// implementace třídy Matice
};
template<int radky>
class Matice<radky, 1> {
// implementace třídy jednosloupcových matic
};
Proces instanciace je u specializované třídy v podstatě stejný, pouze kód je generován z jiné šablony třídy, a to ze specializace:
int main() {
// první třída
Matice<3,3> a;
// částečně specializovaná šablona třídy (druhá třída)
Matice<15,1> b;
}
Je důležité poznamenat, že obě třídy jsou na sobě nezávislé, tj. nedědí od sebe ani konstruktory a destruktory, ani elementární funkce nebo datové prvky.
Specializace funkčních šablon
[editovat | editovat zdroj]Na rozdíl od šablon tříd nelze šablony funkcí podle normy částečně specializovat (pouze plně). Specializace šablon funkcí se však obecně nedoporučuje, protože pravidla pro určení „nejvhodnější“ funkce mohou vést k neočekávaným výsledkům.[4]
Ve většině případů lze přetěžováním šablon funkcí jinými šablonami funkcí dosáhnout stejných výsledků jako částečnou specializací (která není povolena). Samotné rozšíření je obvykle velmi intuitivní:
// Generická funkce
template<class T, class U>
void f(T a, U b) {}
// Přetížené šablona funkce
template<class T>
void f(T a, int b) {}
// Plně specializovaná; ale stále šablona
template<>
void f(int a, int b) {}
Je však třeba poznamenat, že explicitní volání, například f<int,int>()
nevede k požadovanému výsledku. Toto volání by totiž místo plně specializované funkce zavolalo generickou funkci. Volání f<int>()
naproti tomu nevolá první přetíženou šablonovou funkci, ale plně specializovanou. Pokud nepoužijeme explicitní volání, vše funguje normálně tak, jak se zdá logické (f(3, 3)
volá plně specializovanou funkci, f(3.5, 5)
částečně specializovanou (první přetíženou funkci) a f(3.5, 2.0)
generickou). S tímto typem specializace je tedy třeba být opatrný, a pokud možno specializovat úplně.
Pokud tato technika v konkrétním případě není použitelná – např. pokud je třeba specializovat šablonu metod třídy bez rozšíření definice třídy – lze problém specializace přesunout také na šablonu pomocné třídy:
class Priklad {
private:
template<typename T>
struct Hracicka {
static T hraj_si(T param);
};
public:
template<typename T>
T neco_delej(T param);
};
template<typename T>
T Priklad::neco_delej(T param) {
// Skutečnou činnost by měl dělat Hracicka
return Hracicka<T>::hraj_si(param);
}
template<typename T>
T Priklad::Hracicka<T>::hraj_si(T param) {
// Standardní implementace ...
}
template<>
int Priklad::Hracicka<int>::hraj_si(int param) {
// typ int „něco dělá“ jiným způsobem
return (param << 3) + (param % 7) - param + foobar;
}
Parametry šablony
[editovat | editovat zdroj]Šablony mohou mít čtyři druhy parametrů: typové parametry, netypové parametry, parametry šablony a takzvané balíčky parametrů, které lze použít k definici šablon s proměnným počtem parametrů.
Typové parametry
[editovat | editovat zdroj]Šablony s typovými parametry zhruba odpovídají generickým typům jiných programovacích jazyků. Jako typový parametr lze použít libovolný datový typ, přičemž počet typů parametrů, s nimiž lze šablonu instanciovat, je omezen v závislosti na použití typového parametru v rámci šablony.
Kontejnery standardní knihovny C++ používají typové parametry mimo jiné proto, aby poskytly vhodné kontejnery pro všechny datové typy, včetně uživatelsky definovaných.
template<typename T>
class Vector {
public:
Vector(): rep(0) {}
Vector(int _size): rep(new T[_size]), size(_size) {}
~Vector() { delete[] rep; }
private:
T* rep;
int size;
};
Netypové parametry
[editovat | editovat zdroj]Netypové parametry (anglicky non-type template parameter) jsou konstantní hodnoty známé v době překladu, pomocí nichž lze proměnné, procedury nebo predikáty uvést jako parametry šablon. Jako netypové parametry šablon jsou povoleny následující hodnoty:
- celočíselné konstanty (včetně znakových konstant)
- konstanty ukazatelů (ukazatele na data a funkce, včetně ukazatelů na členské proměnné a funkce)
- znakové řetězcové konstanty.
Netypové parametry se používají například jako specifikace velikosti v std::array
nebo jako kritéria třídění a vyhledávání v mnoha algoritmech standardní knihovny, jako je std::sort
, std::find_if
nebo std::for_each
.
Šablony šablon
[editovat | editovat zdroj]Šablony šablon jsou konstrukce, v nichž jedna šablona dostává šablonu jako parametr. Poskytují další abstrakční mechanismus. V následujícím příkladu je specifikován jak typ, tak použitý kontejner; ten je specifikován pomocí parametru šablona šablony:
template <template <typename, typename> class Container, typename Type>
class MujKontejner {
Container<Type, std::allocator <Type> > baz;
};
Příklad použití:
MujKontejner <std::deque, int> muj_kontejner;
Balíčky parametrů
[editovat | editovat zdroj]Od C++11 lze definovat a používat šablony s proměnným počtem parametrů. Stejně jako u funkcí a maker s proměnným počtem parametrů jsou další parametry naznačeny pomocí tří teček: ...
template<typename...Values> class tuple {
// definice vynechána
};
// použití:
tuple<int, int, char, std::vector<int>, double> t;
Odkazy
[editovat | editovat zdroj]Reference
[editovat | editovat zdroj]V tomto článku byl použit překlad textu z článku Template (C++) na německé Wikipedii.
- ↑ STROUSTRUP, Bjarne. Die C++-Programmiersprache. 4. vyd. [s.l.]: Addison-Wesley, 2009. ISBN 978-3-8273-2823-6.
- ↑ Oracle Technology Network for Java Developers|Oracle Technology Network|Oracle [online]. [cit. 2017-05-26]. Dostupné online.
- ↑ An Introduction to C# Generics [online]. [cit. 2017-05-26]. Dostupné online. (anglicky)
- ↑ SUTTER, Herb. Why Not Specialize Function Templates?. C/C++ Users Journal. 2001-07. gotw.ca Dostupné online.
Literatura
[editovat | editovat zdroj]- VANDERVOORDE, David; JOSUTTIS, Nicolai M. C++ Templates. [s.l.]: Addison-Wesley Professional, 2003. Dostupné online. ISBN 0-201-73484-2.