Šablona (C++)

jazyková konstrukce programovacího jazyka C++
(přesměrováno z Šablona (programování))

Š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

V jazyce C++ existují tři druhy šablon: šablony funkcí, třídní šablony a šablony proměnných:

Šablony funkcí

editovat

Š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

Š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

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

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

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

Š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

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

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

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

Š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

Š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

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

Š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

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;

Reference

editovat

V tomto článku byl použit překlad textu z článku Template (C++) na německé Wikipedii.

  1. STROUSTRUP, Bjarne. Die C++-Programmiersprache. 4. vyd. [s.l.]: Addison-Wesley, 2009. ISBN 978-3-8273-2823-6. 
  2. Oracle Technology Network for Java Developers|Oracle Technology Network|Oracle [online]. [cit. 2017-05-26]. Dostupné online. 
  3. An Introduction to C# Generics [online]. [cit. 2017-05-26]. Dostupné online. (anglicky) 
  4. SUTTER, Herb. Why Not Specialize Function Templates?. C/C++ Users Journal. 2001-07. gotw.ca Dostupné online. 

Literatura

editovat

Související články

editovat