Šablony tříd
C++ 11

image_printTisk

Tak, jak lze vytvářet šablony funkcí, lze vytvářet i šablony tříd, struktur a unionů. Pomocí těchto šablon potom překladač generuje nové uživatelsky definované datové typy. Ukážeme si jejich deklaraci, dále pak kam je vhodné takové šablony umisťovat a také neméně důležitá informace bude, jak probíhá syntaktická kontrola těchto šablon.

Bylo již zmíněno, že v jazyce C++ můžeme vytvářet šablony tříd, struktur a unionů. Nejběžnější je ale tvorba šablon tříd. Tak jako šablona funkce je vzor, ze kterého se generuje funkce nová, stejně tak je šablona třídy jakýmsi “meta-vzorem” pro třídy. Při dodání příslušných parametrů šablony, ať již datových typů nebo celých čísel, generuje překladač z těchto vzorů nové datové typy. Šablony tříd se například používají pro kontejnerové třídy, jak si ukážeme na následujícím příkladu.

#include <iostream>
using namespace std;

const int MAX_POLOZEK = 10;

class ZasobnikJePlny {};
class ZasobnikJePrazdny {};

template<typename T>
class Zasobnik {
public:
    T polozky[MAX_POLOZEK];
    int vrchol;
    
    Zasobnik() {
        vrchol = 0;
    }
    
    void vloz(T hodnota) {
        if(jePlny()) 
            throw new ZasobnikJePlny();
        polozky[vrchol++] = hodnota;
    }
    
    bool jePrazdny() {
        return vrchol == 0;
    }
    
    bool jePlny();
    T vyndej();
};

template<typename T>
bool Zasobnik<T>::jePlny() {
    return vrchol >= MAX_POLOZEK;
}

template<typename T>
T Zasobnik<T>::vyndej() {
    if(jePrazdny())
        throw new ZasobnikJePrazdny();
    return polozky[--vrchol];
}

int main() {
    int i = 0;
    Zasobnik<int> zasobnik;
    
    while(! zasobnik.jePlny())
        zasobnik.vloz(++i);
        
    while(! zasobnik.jePrazdny())
        cout << zasobnik.vyndej() << endl;
        
    return 0;
}

Na příkladu je šablona třídy implementující klasický zásobník, pro jednoduchost, s omezeným počtem položek, které může obsahovat. Příklad začíná definicí konstanty MAX_POLOZEK udávající maximální velikost zásobníku a dále dvě třídy reprezentující výjimky, které jsou použity v případě, kdy nelze do zásobníku už žádnou položku vložit nebo vyjmout.

Pak následuje již definice vlastní šablony. Jako u šablon funkcí, i v tomto případě začínáme definici klíčovým slovem template. Za ním pak je zase výčet parametrů šablony uvozených znaky < a >. V našem případě je tu jeden parametr T udávající datový typ, se kterým zásobník bude pracovat. Parametr reprezentující datový typ musí být opět uvozen klíčovým slovem typename.  Za touto deklarací šablony pak již následuje klasická definice třídy, jak ji známe.

Uvnitř třídy jsou dva důležité atributy. Prvním z nich je klasické pole jménem polozky typované pomocí parametru  T a pak proměnná vrchol udávající index ve jmenovaném poli, kam se bude vkládat položka nová. Jinými slovy, se jedná o index, který následuje za poslední vloženou položkou.

Na příkladu jsou pak ukázány oba způsoby zápisu metod. Prvním je uvnitř definice šablony třídy, druhý vně. V případě způsobu zápisu uvnitř šablony třídy je definice totožná jako u metody klasické třídy. Jen místo konkrétního datového typu položky zásobníku se pracuje se zástupným parametrem T. Prvním způsobem jsou implementovány metody vloz a jePrazdny.

U druhých dvou metod, tedy jePlnyvyndej, je použit druhý způsob definice, a tedy je uvnitř šablony třídy umístěna jen deklarace těchto metod. Jejich definice následuje za tělem třídy. I u těchto definic musíme napřed uvést plnou deklaraci šablony, tedy uvození klíčovým slovem template s následným seznamem parametrů, a po té následuje klasická definice metody s jedním rozdílem. Za jménem třídy musíme uvést parametr šablony opět uvozený znaky < a >, tentokrát ale bez klíčového slova typename.

Na závěr, instancování našeho zásobníku ve funkci main je již velmi jednoduchý. Zapíšeme název šablony, do znaků < a > uvedeme konkrétní datový typ a pak pokračujeme zápisem identifikátoru proměnné (samozřejmě v případě kdy alokujeme naši šablonu na systémovém zásobníku, jako v našem příkladě).

Nyní si vysvětlíme způsob, jak umisťovat šablony tříd do souborů. Je zde podstatný rozdíl od klasických tříd. První je nutné chápat, že šablona třídy není přímo syntaktickým prvkem, který reprezentuje konkrétní kód ke kompilaci. Představuje pro překladač v podstatě takový vzor, jak generovat nové datové typy, které jsou pak následně překládány do binárního kódu.

Z tohoto důvodu překladač potřebuje všechny informace o šabloně, včetně definic metod vně definice třídy. Proto, pokud má být šablona sdílená více CPP soubory, musí být celá její definice umístěna v hlavičkovém souboru včetně metod vně funkce. V případě, pokud slouží jen pro jeden konkrétní modul, lze ji umístit do CPP souboru.

V případě, že bychom rozdělili šablonu do hlavičkového a CPP souboru, museli bychom v direktivě #include vložit oba tyto soubory.

Poslední téma, které nastíníme, je proces kompilace šablon a možného výskytu chyb. První proběhne “hrubá” syntaktická kontrola šablony. Další kontrola chyb následuje při instancování šablony, a to z toho hlediska, zda lze na základě dodaných parametrů šablony nový datový typ třídy ze šablony vygenerovat. Nakonec je důležité vědět, že se instancováním třídy negenerují za šablony všechny její metody. Ty vznikají až jejích použitím.

Proto se mohou chyby objevovat chyby u šablon tříd následujícím postupem. Napřed hrubým překladem šablony bez jejího instancování, pak jejím instacováním v CPP souboru a nakonec je třeba počítat s možnými chybami při volání konkrétních metod. TODO: JEŠTĚ RADŠI OVĚŘIT.

Na závěr zmíníme, že šablony tříd nelze přetěžovat jako šablony funkcí. Můžeme je ale specializovat a o tom si povíme v následujícím článku.

Shrnutí:

TODO:

  1. Ověření faktů o ověřování chyb, hlavně překladu metod.
  2. Úprava posledních odstavců.
  3. Napsat shrnutí.
image_printTisk
Šablony tříd
C++ 11
Ohodnoťte tento článek

Související články

  • Šablony funkcí I Tímto článkem si představíme nový pojem, a to je generické programování v C++. Jedná se o způsob programování, jehož základní znalost je nutná pro použití jedné z nejdůležitějších knihoven […]
  • Šablony funkcí II V tomto článku dokončíme výklad o šablonách funkcí. Zmíníme se o tom, že šablony mohou obsahovat kromě parametrů reprezentujících datové typy také celočíselné hodnoty a dále si představíme […]
  • Formátování textů na textové konzoli V tomto článku si popíšeme, jak provádět formátovaný výstup na textovou konzoli. Výše uvedené způsoby formátování výstupu lze ale použít, jak se dále dovíme, i při zápisu do souboru nebo […]
  • Konstanty Tento článek si klade za cíl vysvětlit význam a princip fungování konstant v jazyce C++. Seznámíme se s důvody jejich zavedení, uvedu zde dvě formy jejich zápisu a krátce záměrně odbočím […]
  • Vícerozměrná pole Tento článek pojednává o vícerozměrných polích v jazyce C++. Seznámíme se s jejich významem, deklarací, inicializací a způsobem zápisu a čtení jejich hodnot. Na závěr článku bude zmíněno, […]
  • Vstup a výstup pomocí textové konzole V tomto článku se velmi podrobně seznámíme se vstupem a výstupem na textovou konzoli. Začneme popisem jednoduchého vstupu a výstupu a pak dále navážeme, jak se pomocí speciálních příkazů, […]