Sdílet prostřednictvím


Šablony (C++)

Šablony jsou základem obecného programování v jazyce C++. Jazyk C++ jako jazyk silného typu vyžaduje, aby všechny proměnné měly konkrétní 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í.

Definování a používání šablon

Šablona je konstrukce, která vygeneruje běžný typ nebo funkci v době kompilace na základě argumentů, které uživatel poskytuje pro parametry šablony. Můžete například definovat šablonu funkce takto:

template <typename T>
T minimum(const T& lhs, const T& rhs)
{
    return lhs < rhs ? lhs : rhs;
}

Výše uvedený kód popisuje šablonu obecné funkce s jedním parametrem typu T, jehož návratová hodnota a parametry volání (lhs a rhs) jsou všechny tohoto typu. Parametr typu můžete pojmenovat cokoliv, co se vám líbí, ale nejčastěji se používají velká písmena s jedním velkými písmeny podle konvence. T je parametr šablony; typename klíčové slovo říká, že tento parametr je zástupný symbol pro typ. Při zavolání funkce kompilátor nahradí každou instanci konkrétního T typu argument, který je buď určen uživatelem, nebo vyvolá kompilátor. Proces, při kterém kompilátor generuje třídu nebo funkci ze šablony, se označuje jako vytvoření instance šablony; minimum<int> je instance šablony minimum<T>.

Jinde může uživatel deklarovat instanci šablony, která je specializovaná pro int. Předpokládejme, že get_a() a get_b() jsou funkce, které vrací int:

int a = get_a();
int b = get_b();
int i = minimum<int>(a, b);

Vzhledem k tomu, že se jedná o šablonu funkce a kompilátor může odvodit typ T argumentů a a b, můžete ho volat stejně jako obyčejnou funkci:

int i = minimum(a, b);

Když kompilátor narazí na poslední příkaz, vygeneruje novou funkci, ve které je každý výskyt T v šabloně nahrazen:int

int minimum(const int& lhs, const int& rhs)
{
    return lhs < rhs ? lhs : rhs;
}

Pravidla pro způsob, jakým kompilátor provádí odpočty typů v šablonách funkcí, jsou založená na pravidlech pro běžné funkce. Další informace naleznete v tématu Přetížení rozlišení volání šablony funkce.

Parametry typu

minimum Ve výše uvedené šabloně si všimněte, že parametr typu T není kvalifikován žádným způsobem, dokud se nepoužije v parametrech volání funkce, kde jsou přidány const a referenční kvalifikátory.

Počet parametrů typu není nijak praktický. Více parametrů oddělte čárkami:

template <typename T, typename U, typename V> class Foo{};

Klíčové slovo class je v tomto kontextu ekvivalentní typename . Předchozí příklad můžete vyjádřit takto:

template <class T, class U, class V> class Foo{};

Pomocí operátoru tří teček (...) můžete definovat šablonu, která přebírá libovolný počet nulových nebo více parametrů typu:

template<typename... Arguments> class vtclass;

vtclass< > vtinstance1;
vtclass<int> vtinstance2;
vtclass<float, bool> vtinstance3;

Libovolný předdefinovaný nebo uživatelem definovaný typ lze použít jako argument typu. Můžete například použít std::vector ve standardní knihovně k ukládání proměnných typu int, doublestd ::string, MyClass* const MyClassa MyClass&tak dále. Primárním omezením při použití šablon je, že argument typu musí podporovat všechny operace použité na parametry typu. Pokud například voláme minimum jako MyClass v tomto příkladu:

class MyClass
{
public:
    int num;
    std::wstring description;
};

int main()
{
    MyClass mc1 {1, L"hello"};
    MyClass mc2 {2, L"goodbye"};
    auto result = minimum(mc1, mc2); // Error! C2678
}

Vygeneruje se chyba kompilátoru, protože MyClass neposkytuje přetížení operátoru < .

Neexistuje žádný základní požadavek, že argumenty typu pro každou konkrétní šablonu patří do stejné hierarchie objektů, i když můžete definovat šablonu, která takové omezení vynucuje. Můžete kombinovat objektově orientované techniky se šablonami; Můžete například uložit odvozený* v vektorové<bázi*>. Všimněte si, že argumenty musí být ukazatele.

vector<MyClass*> vec;
   MyDerived d(3, L"back again", time(0));
   vec.push_back(&d);

   // or more realistically:
   vector<shared_ptr<MyClass>> vec2;
   vec2.push_back(make_shared<MyDerived>());

Základní požadavky, které std::vector a další standardní kontejnery knihoven ukládají na prvky T , je, že T je možné přiřadit kopírování a kopírovat-konstruktible.

Parametry bez typu

Na rozdíl od obecných typů v jinýchchm Můžete například zadat celočíselnou hodnotu konstanty, která určuje délku pole, stejně jako v tomto příkladu, který je podobný třídě std::array ve standardní knihovně:

template<typename T, size_t L>
class MyArray
{
    T arr[L];
public:
    MyArray() { ... }
};

Poznamenejte si syntaxi v deklaraci šablony. Hodnota size_t je předána jako argument šablony v době kompilace a musí být const nebo constexpr výraz. Používáte ho takto:

MyArray<MyClass*, 10> arr;

Jiné typy hodnot, včetně ukazatelů a odkazů, lze předat jako netypové parametry. Můžete například předat ukazatel na funkci nebo objekt funkce a přizpůsobit nějakou operaci uvnitř kódu šablony.

Odpočty typů pro parametry šablony bez typu

V sadě Visual Studio 2017 a novějším a v /std:c++17 režimu nebo novějším kompilátor odvodí typ netypového argumentu šablony deklarovaného pomocí auto:

template <auto x> constexpr auto constant = x;

auto v1 = constant<5>;      // v1 == 5, decltype(v1) is int
auto v2 = constant<true>;   // v2 == true, decltype(v2) is bool
auto v3 = constant<'a'>;    // v3 == 'a', decltype(v3) is char

Šablony jako parametry šablon

Šablona může být parametrem šablony. V tomto příkladu má MyClass2 dva parametry šablony: parametr typename T a parametr šablony Arr:

template<typename T, template<typename U, int I> class Arr>
class MyClass2
{
    T t; //OK
    Arr<T, 10> a;
    U u; //Error. U not in scope
};

Protože samotný parametr Arr nemá žádný text, jeho názvy parametrů nejsou potřeba. Ve skutečnosti se jedná o chybu odkazující na název typu nebo názvy parametrů třídy Arr z textu MyClass2souboru . Z tohoto důvodu je možné vynechat názvy parametrů typu Arr, jak je znázorněno v tomto příkladu:

template<typename T, template<typename, int> class Arr>
class MyClass2
{
    T t; //OK
    Arr<T, 10> a;
};

Výchozí argumenty šablony

Šablony tříd a funkcí můžou mít výchozí argumenty. Pokud má šablona výchozí argument, můžete ho nechat nezadanou, když ji použijete. Například šablona std::vector má výchozí argument pro alokátor:

template <class T, class Allocator = allocator<T>> class vector;

Ve většině případů je přijatelná výchozí třída std::allocator, takže použijete vektor podobný tomuto:

vector<int> myInts;

V případě potřeby ale můžete zadat vlastní alokátor takto:

vector<int, MyAllocator> ints;

U více argumentů šablony musí mít všechny argumenty za prvním výchozím argumentem výchozí argumenty výchozí argumenty.

Při použití šablony, jejíž parametry jsou všechny výchozí, použijte prázdné úhlové závorky:

template<typename A = int, typename B = double>
class Bar
{
    //...
};
...
int main()
{
    Bar<> bar; // use all default type arguments
}

Specializace šablon

V některých případech není možné ani žádoucí, aby šablona definovala přesně stejný kód pro libovolný typ. Například můžete chtít definovat cestu kódu, která se má provést pouze v případě, že argument typu je ukazatel, nebo std::wstring, nebo typ odvozený z konkrétní základní třídy. V takových případech můžete definovat specializaci šablony pro daný typ. Když uživatel vytvoří instanci šablony s tímto typem, kompilátor použije specializaci ke generování třídy a pro všechny ostatní typy kompilátor zvolí obecnější šablonu. Specializace, ve kterých jsou všechny parametry specializované, jsou kompletní specializace. Pokud jsou specializované jenom některé parametry, nazývá se částečná specializace.

template <typename K, typename V>
class MyMap{/*...*/};

// partial specialization for string keys
template<typename V>
class MyMap<string, V> {/*...*/};
...
MyMap<int, MyClass> classes; // uses original template
MyMap<string, MyClass> classes2; // uses the partial specialization

Šablona může mít libovolný počet specializace, pokud je každý specializovaný parametr typu jedinečný. Pouze šablony tříd můžou být částečně specializované. Všechny úplné a částečné specializace šablony musí být deklarovány ve stejném oboru názvů jako původní šablona.

Další informace naleznete v tématu Specializace podle šablon.