1.1 Co je to šablona - template?
Mechanismus šablon v C++ někdy také nazývaný jako genericita, dovoluje
programátorům vytvářet parametrizované moduly, jejichž parametrem může být i
nějaký blíže neurčený (primitivní, nebo objektový) typ. Co přesně tato
komplikovaná definice znamená si ukážeme na jednoduchém příkladu. Představme si,
že vytváříme kolekci objektů a snažíme se určit typ prvků, které tato kolekce
bude obsahovat. Máme samozřejmě několik možností.
Typ prvků může být napevno určený. Pokud použijeme
kupříkladu typ int, zříkáme se tak možnosti vkládat do seznamu jiné
hodnoty než celočíselné. Pokud budeme chtít v budoucnu vytvořit například kolekci
čísel s pohyblivou desetinnou čárkou musíme vytvořit další kolekci. Smířit se
asi budeme muset s tím, že zdrojový kód obou kolekcí bude velmi podobný (možná
skoro úplně stejný) a lišit se bude pouze použitým typem.
Jako typ můžeme použít obecný ukazatel void. V tomto
případě lze sice do kolekce vkládat různé typy objektů, ale při přístupu k
těmto objektům musíme vždy provádět přetypování, což nikdy není bezpečná
akce.
Další možností je vytvoření abstraktního typu (abstraktní
třídy) a odkazovat se pomocí něj na prvky v kolekci. V tomto okamžiku můžeme
vkládat do kolekce instance těch tříd, které dědí (přímo, nebo nepřímo) z této
abstraktní třídy (využíváme zde polymorphism). Pro instance tříd z
jiných hierarchií však musíme vytvářet další kolekce.
Ideálním řešením by bylo vytvoření takové třídy, která by
typ prvků určovala ve svém parametru. Vytvořit jakýsi parametrizovaný vzor, podle
kterého by se vytvářely skutečné třídy. A právě v tuto chvíli se dostávají ke
slovu šablony - templates.
V jazyce C++ existují dva typy šablon, šablony funkcí
(function templates) a šablony tříd (class templates).
Můžeme však říci, že ty pravé výhody použití šablon jsou patrné především
při použití šablon tříd.
1.2 Výhody šablon
Jednou z velkých výhod je skutečnost, že pouhým vytvořenám šablony ještě
nevzniká binární kód. Odpovídající binární kód je kompilátorem vytvořený až
v okamžiku dosazení parametrů a vytvoření instance šabony (tedy třídy, nebo
funkce) a její následné použití. Tento princip zajišťuje, že ve výsledném
přeloženém souboru je obsažen pouze ten kód, který skutečně používáme (to
může být při použití velkých knihoven šablon ohromná úspora). Vzhledem k tomu,
že jsou šablony distribuovány s kompletním zdrojovým kódem, může dobrý
kompilátor dále optimalizovat kód i tím, že vynechá veškerý nepoužitý kód
(vždy se najde nějaká metoda třídy, kterou nepoužijeme).
Příkladem knihoven šablon může být ATL - Active Template
Library, která je určena pro vývoj COM objektů, nebo STL - Standard template
library, jež je distribuována s knihovnou MFC.
1.3 Šablony funkcí
Pomocí šablon funkcí můžeme definovat množinu funkcí se stejným kódem, které
používají různé datové typy. Prvním příkladem bude jednoduchá šablona funkce
pro zjištění maxima ze dvou proměnných blíže neurčeného typu.
template <typename TYPE> TYPE Max(TYPE a, TYPE b)
{ return (a > b) ? a : b; } |
Šablona očekává jediný parametr TYPE, který určuje typ
dvou porovnávaných parametrů funkce a typ návratové hodnoty funkce. Šablona
předpokládá, že pro dosazený typ bude definován operátor ">".
Pozorného čtenáře jistě napadne, že typ může být jak primitivní, tak uživatelem
definovaný objektový.
Ve starších verzích se místo klíčového slova typename
používalo klíčové slovo class. Domnívám se však, že tato změna byla
provedena správně, protože klíčové slovo class bylo zavádějící.
Použití šablony funkce (tj. vytvoření instance - funkce) je
jednoduché a spočívá v jejím zavolání. Podívejme se na krátkou ukázku.
int a=10, b=20;
int c = Max(a, b);
double d1=10.1, d2= 20.2;
double d3 = Max(d1, d2);
printf("Maxima: %d, %f\n", c, d3); |
V předchozím příkladu je vidět použití šablony pro různé
datové typy. V tuto chvíli je také kompilátorem vygenerovaný odpovídající kód
dvou funkcí, pro typ int a pro typ double. Pokud explicitně neuvedeme
při volání funkce datový typ (náš případ), neprovádí kompilátor žádné
přetypování a typ parametrů musí být samozřejmě stejný. Následující ukázka
zobrazuje chybné použití šablony.
Tento případ však můžeme opravit explicitním určením parametru,
který zajistí automatickou typovou konverzi.
double d = Max<double>(1, 1.1); |
Bez použití mechanismu šablon funkcí bychom museli definovat dvě
typově-bezpečné funkce, viz. následující ukázka.
int Max(int a, int b) { return (a < b) ? a : b; }
double Max(double a, double b) { return (a < b) ? a : b; } |
1.4 Šablona vs. makro
Předchozí příklad lze vyřešit také pomocí maker. Mějme však na paměti zásadní
rozdíl mezi makrem a šablonou. Makra jsou řešena textovým preprocesorem jazyka, tj.
jedná se pouze o jednoduché dosazování textu, kdežto šablony jsou vyhodnocovány
kompilátorem.
Pro zjištění maxima za dvou proměnných bychom mohli použít
následující makro.
#define MAX(a, b) (((a) > (b)) ? (a) : (b)) |
Je zde však několik zásadních rozdílů:
1.5 Další příklad šablony funkce
Dalším příkladem použití šablony funkce může být kupříkladu funkce, která
vymění hodnoty dvou parametrů. Podívejme se na následující příklad.
template <class TYPE> void Swap( TYPE& a, TYPE& b )
{
TYPE c(a);
a = b;
b = c;
} |
Použití šablony, která řeší výměnu hodnot je ideální,
protože kompilátor kontroluje typy parametrů a zajišťuje tak jejich shodnost již v
okamžiku kompilace (v našem případě je typová kontrola velice důležitá).
Použití této šablony je opět znázorněno na následující
ukázce.
double d = 10.0;
int a=10, b=20;
Swap(a, b); // OK
Swap(a, d); // Error - různé typy
printf("a=%d, b=%d\n", a, b); |
Jak již bylo řečeno parametrem šablony může být i objektový,
uživatelem definovaný, typ. Prohlédněme si opět následující ukázku zdrojového
kódu.
CMyObject o1, o2;
Swap(o1, o2); |
Příště šablony tříd... |