Szablony (C++)

Szablony są podstawą programowania ogólnego w języku C++. Jako silnie typizowanego języka język C++ wymaga, aby wszystkie zmienne miały określony typ, jawnie zadeklarowane przez programistę lub wywołane przez kompilator. Jednak wiele struktur danych i algorytmów wygląda tak samo niezależnie od typu, na jakim działają. Szablony umożliwiają zdefiniowanie operacji klasy lub funkcji i umożliwienie użytkownikowi określenia konkretnych typów, na których te operacje powinny działać.

Definiowanie i używanie szablonów

Szablon to konstrukcja, która generuje zwykły typ lub funkcję w czasie kompilacji na podstawie argumentów, które użytkownik dostarcza dla parametrów szablonu. Na przykład można zdefiniować szablon funkcji w następujący sposób:

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

Powyższy kod opisuje szablon funkcji ogólnej z pojedynczym typem parametrU T, którego wartość zwracana i parametry wywołania (lhs i rhs) są tego typu. Możesz nazwać parametr typu dowolny, ale zgodnie z konwencją najczęściej używane są pojedyncze wielkie litery. T jest parametrem szablonu; typename słowo kluczowe mówi, że ten parametr jest symbolem zastępczym dla typu. Po wywołaniu funkcji kompilator zastąpi każde wystąpienie T konkretnego argumentu typu określonego przez użytkownika lub wywołane przez kompilator. Proces, w którym kompilator generuje klasę lub funkcję na podstawie szablonu, jest określany jako wystąpienie szablonu; minimum<int> jest wystąpieniem szablonu minimum<T>.

W innym miejscu użytkownik może zadeklarować wystąpienie szablonu, które jest wyspecjalizowane dla int. Załóżmy, że get_a() i get_b() to funkcje zwracające int:

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

Jednak ponieważ jest to szablon funkcji, a kompilator może wyłudzać typ T argumentów a i b, można wywołać go tak samo jak zwykła funkcja:

int i = minimum(a, b);

Gdy kompilator napotka tę ostatnią instrukcję, generuje nową funkcję, w której każde wystąpienie języka T w szablonie jest zastępowane elementem int:

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

Reguły dotyczące sposobu, w jaki kompilator wykonuje potrącenie typu w szablonach funkcji, są oparte na regułach dla zwykłych funkcji. Aby uzyskać więcej informacji, zobacz Przeciążenie rozpoznawania wywołań szablonów funkcji.

Parametry typu

W powyższym szablonie minimum należy pamiętać, że parametr typu T nie jest kwalifikowany w żaden sposób, dopóki nie zostanie użyty w parametrach wywołania funkcji, gdzie dodawane są kwalifikatory const i odwołania.

Nie ma praktycznego limitu liczby parametrów typu. Oddzielaj wiele parametrów przecinkami:

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

Słowo kluczowe class jest równoważne typename w tym kontekście. Możesz wyrazić poprzedni przykład jako:

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

Możesz użyć operatora wielokropka (...) do zdefiniowania szablonu, który przyjmuje dowolną liczbę parametrów zero lub więcej typów:

template<typename... Arguments> class vtclass;

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

Każdy wbudowany lub zdefiniowany przez użytkownika typ może służyć jako argument typu. Na przykład można użyć metody std::vector w bibliotece standardowej do przechowywania zmiennych typu int, , doublestd::string, MyClass, constMyClass*, MyClass&itd. Podstawowym ograniczeniem w przypadku używania szablonów jest to, że argument typu musi obsługiwać wszystkie operacje, które są stosowane do parametrów typu. Jeśli na przykład wywołamy minimum metodę using MyClass , jak w tym przykładzie:

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
}

Zostanie wygenerowany błąd kompilatora, ponieważ MyClass nie zapewnia przeciążenia operatora < .

Nie ma właściwego wymagania, że argumenty typu dla dowolnego określonego szablonu należą do tej samej hierarchii obiektów, chociaż można zdefiniować szablon, który wymusza takie ograniczenie. Techniki obiektowe można łączyć z szablonami; na przykład można przechowywać pochodne* w bazie wektorowej<*>. Należy pamiętać, że argumenty muszą być wskaźnikami

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>());

Podstawowe wymagania, które std::vector i inne standardowe kontenery bibliotek nakładają na elementy T , to możliwość T przypisywania kopii i tworzenia kopii.

Parametry inne niż typ

W przeciwieństwie do typów ogólnych w innych językach, takich jak C# i Java, szablony języka C++ obsługują parametry nietypowe, nazywane również parametrami wartości. Można na przykład podać stałą wartość całkowitą, aby określić długość tablicy, tak jak w tym przykładzie, który jest podobny do klasy std::array w bibliotece standardowej:

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

Zanotuj składnię w deklaracji szablonu. Wartość size_t jest przekazywana jako argument szablonu w czasie kompilacji i musi być const wyrażeniem constexpr lub . Użyjesz go w następujący sposób:

MyArray<MyClass*, 10> arr;

Inne rodzaje wartości, w tym wskaźniki i odwołania, mogą być przekazywane jako parametry inne niż typ. Na przykład można przekazać wskaźnik do obiektu funkcji lub funkcji, aby dostosować operację wewnątrz kodu szablonu.

Potrącenie typu dla parametrów szablonu innego niż typ

W programie Visual Studio 2017 lub nowszym oraz w /std:c++17 trybie lub nowszym kompilator deduuje typ argumentu szablonu innego niż typ zadeklarowany za pomocą autopolecenia :

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

Szablony jako parametry szablonu

Szablon może być parametrem szablonu. W tym przykładzie myClass2 ma dwa parametry szablonu: parametr typename T i parametr szablonu 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
};

Ponieważ sam parametr Arr nie ma treści, jego nazwy parametrów nie są potrzebne. W rzeczywistości jest to błąd, aby odwołać się do nazwy typename lub parametrów klasy Arr z wewnątrz treści MyClass2. Z tego powodu nazwy parametrów typu Arr można pominąć, jak pokazano w tym przykładzie:

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

Domyślne argumenty szablonu

Szablony klas i funkcji mogą mieć argumenty domyślne. Jeśli szablon ma argument domyślny, możesz pozostawić go nieokreślony podczas jego używania. Na przykład szablon std::vector ma domyślny argument dla alokatora:

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

W większości przypadków domyślna klasa std::allocator jest akceptowalna, więc użyj wektora w następujący sposób:

vector<int> myInts;

W razie potrzeby można jednak określić niestandardowy alokator w następujący sposób:

vector<int, MyAllocator> ints;

W przypadku wielu argumentów szablonu wszystkie argumenty po pierwszym argumencie domyślnym muszą mieć argumenty domyślne.

W przypadku korzystania z szablonu, którego parametry są domyślne, użyj pustych nawiasów kątowych:

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

Specjalizacja szablonu

W niektórych przypadkach nie jest możliwe ani pożądane, aby szablon zdefiniował dokładnie ten sam kod dla dowolnego typu. Na przykład możesz zdefiniować ścieżkę kodu, która ma być wykonywana tylko wtedy, gdy argument typu jest wskaźnikiem lub std::wstring lub typem pochodzącym z konkretnej klasy bazowej. W takich przypadkach można zdefiniować specjalizację szablonu dla danego typu. Gdy użytkownik tworzy wystąpienie szablonu z tym typem, kompilator używa specjalizacji do generowania klasy, a dla wszystkich innych typów kompilator wybiera bardziej ogólny szablon. Specjalizacje, w których wszystkie parametry są wyspecjalizowane, to kompletne specjalizacje. Jeśli tylko niektóre parametry są wyspecjalizowane, jest nazywana częściową specjalizacją.

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

Szablon może mieć dowolną liczbę specjalizacji, o ile każdy wyspecjalizowany parametr typu jest unikatowy. Tylko szablony klas mogą być częściowo wyspecjalizowane. Wszystkie kompletne i częściowe specjalizacje szablonu muszą być zadeklarowane w tej samej przestrzeni nazw co oryginalny szablon.

Aby uzyskać więcej informacji, zobacz Specjalizacja szablonu.