Přeskočit na obsah

Class Templates & CTAD

Vytvářejte obecné datové struktury pomocí šablon tříd. Pochopte definice členů, výchozí argumenty a to, jak vám CTAD C++17 umožňuje vynechat argumenty šablony.

Šablony tříd

Šablona třídy definuje rodinu tříd parametrizovaných jedním nebo více typy (nebo netypovými hodnotami). Standardní knihovna je postavena na šablonách tříd:std::vector,std::map,std::optional,std::shared_ptr. Když napíšetestd::vector, kompilátor vytvoří instanci celé třídy sT = inta vygeneruje konkrétní typ.

Šablony tříd se řídí stejnými pravidly jako šablony funkcí: musí být definovány v záhlaví a každá jedinečná sada argumentů šablony vytváří odlišný typ.StackaStackjsou zcela nesouvisející typy – nesdílejí žádný vztah dědičnosti.

Obecný zásobník

Zde je jednoduchý obecný zásobník. Všimněte si, jak členské funkce definované mimo tělo třídy vyžadují opakování seznamu parametrů šablony.

stack.hpp
#include <vector>
#include <stdexcept>
#include <iostream>

template <typename T>
class Stack {
private:
    std::vector<T> elements_;

public:
    void push(const T& value);
    void pop();
    const T& top() const;
    bool empty() const { return elements_.empty(); }
    std::size_t size() const { return elements_.size(); }
};

// Member definitions outside the class require the template header
template <typename T>
void Stack<T>::push(const T& value) {
    elements_.push_back(value);
}

template <typename T>
void Stack<T>::pop() {
    if (elements_.empty()) {
        throw std::out_of_range("Stack<>::pop(): empty stack");
    }
    elements_.pop_back();
}

template <typename T>
const T& Stack<T>::top() const {
    if (elements_.empty()) {
        throw std::out_of_range("Stack<>::top(): empty stack");
    }
    return elements_.back();
}

int main() {
    Stack<int> intStack;
    intStack.push(42);
    intStack.push(17);
    std::cout << intStack.top() << '\n'; // 17
    intStack.pop();
    std::cout << intStack.top() << '\n'; // 42
}

Více parametrů a výchozích hodnot

Šablony tříd mohou mít více parametrů šablon a argumenty výchozí šablony, stejně jako parametry funkcí mohou mít výchozí hodnoty. Výchozí argumenty jsou řešeny zleva doprava.

stack_defaults.cpp
#include <vector>
#include <deque>
#include <iostream>

// Container defaults to std::deque<T> — matches std::stack's design
template <typename T, typename Container = std::deque<T>>
class Stack {
private:
    Container elements_;

public:
    void push(const T& value) { elements_.push_back(value); }
    void pop()                { elements_.pop_back(); }
    const T& top() const      { return elements_.back(); }
    bool empty() const        { return elements_.empty(); }
};

int main() {
    Stack<int> s1;                         // uses deque<int>
    Stack<int, std::vector<int>> s2;       // uses vector<int>

    s1.push(10);
    s2.push(20);
    std::cout << s1.top() << '\n'; // 10
    std::cout << s2.top() << '\n'; // 20
}

Odpočet argumentu šablony třídy (CTAD)

Před C++17 jste při vytváření instancí šablony třídy vždy museli zadat argumenty šablony:std::pair p(1, 3.14);. CTAD (Class Template Argument Deduction), představený v C++17, umožňuje kompilátoru odvodit argumenty šablony třídy z argumentů konstruktoru, stejně jako dedukce argumentů šablony funkcí.

S CTAD:std::pair p(1, 3.14);— kompilátor odvodístd::pair. Toto funguje prostd::vector,std::tuple,std::optional,std::array(C++20) a vaše vlastní šablony tříd.

CTAD používá vodítka dedukcí — buď implicitní (vygenerované z konstruktorů) nebo explicitní (definované uživatelem). Kompilátor vyzkouší každého průvodce a vybere nejlepší shodu.

Příručky CTAD a dedukcí

Uživatelsky definované průvodce dedukcí říkají kompilátoru, jak mapovat argumenty konstruktoru na argumenty šablony. Jsou zvláště užitečné, když typ argumentu konstruktoru přímo neodpovídá parametru šablony.

ctad_example.cpp
#include <iostream>
#include <cstddef>

template <typename T, std::size_t N>
struct Array {
    T data[N];
};

// Without a deduction guide, CTAD cannot deduce T and N.
// This explicit deduction guide maps brace-init with N elements
// of the same type T to Array<T, N>.
template <typename First, typename... Rest>
Array(First, Rest...) -> Array<First, 1 + sizeof...(Rest)>;

// Standard library example: std::vector CTAD from iterators
#include <vector>
#include <list>

int main() {
    // CTAD with our custom deduction guide
    Array arr{1, 2, 3, 4, 5};  // deduces Array<int, 5>
    std::cout << arr.data[2] << '\n'; // 3

    // Standard library CTAD examples (C++17)
    std::pair p(42, 3.14);       // pair<int, double>
    std::vector v{1, 2, 3};     // vector<int>
    std::tuple t(1, 2.0, 'a');  // tuple<int, double, char>

    // CTAD from iterator range
    std::list<int> lst{10, 20, 30};
    std::vector v2(lst.begin(), lst.end()); // vector<int>
}

Členské šablony

Členskými funkcemi šablony třídy mohou být samy o sobě šablony, které přidávají další parametry šablony. Toto se nazývá členská šablona. Běžným případem použití je povolení přiřazení nebo konstrukce ze související, ale odlišné instance.

Můžete například chtít přiřaditStackzStack— ale protože se nejedná o nesouvisející typy, výchozí přiřazení kopie nebude fungovat. Operátor přiřazení členská šablona to řeší:

template <typename T>
class Stack {
public:
    template <typename U>
    Stack& operator=(const Stack<U>& other);
};

Inside this member template, U may differ from T. The body can copy elements from other as long as U is convertible to T. This pattern is used extensively in the standard library — std::shared_ptr converts to std::shared_ptr prostřednictvím konstruktoru šablony členů.

Úskalí

Když specializujete šablonu třídy, specializace je zcela nová třída — nedědí žádné členy z primární šablony. Musíte předefinovat každou členskou funkci a datový člen. Zapomenete-li předefinovat člena ve specializaci, dojde při použití tohoto člena k chybě kompilace. Částečná specializace je také dostupná pouze pro šablony tříd, nikoli šablony funkcí. Specializaci se podrobně věnujeme v další kapitole.

Klíčové poznatky
  • Šablony tříd definují rodiny tříd – každá jedinečná sada argumentů vytváří odlišný typ
  • Členské funkce definované mimo třídu musí opakovat hlavičkutemplate a používatClassName::
  • Výchozí argumenty šablony fungují zleva doprava, stejně jako argumenty výchozí funkce
  • CTAD (C++17) umožňuje kompilátoru odvodit argumenty šablony třídy z argumentů konstruktoru
  • Explicitní vodítka dedukcí mapují vzory argumentů konstruktoru na argumenty šablony
  • Šablony členů umožňují operace napříč různými instancemi stejné šablony třídy

Kvíz — Otestujte své znalosti

(15 XP)

1. Vzhledem k`template <typename T, typename Container = std::deque<T>> class Stack`, jaký je typ`Stack<int>`?

2. Co je CTAD (C++17)?

3. Když specializujete šablonu třídy, co se stane s členy primární šablony?