Szukaj…


Wprowadzenie

Klasy, funkcje i (od C ++ 14) zmienne mogą być szablonowane. Szablon jest fragmentem kodu z pewnymi wolnymi parametrami, które staną się konkretną klasą, funkcją lub zmienną, gdy wszystkie parametry zostaną określone. Parametry mogą być typami, wartościami lub samymi szablonami. Dobrze znanym szablonem jest std::vector , który staje się konkretnym typem kontenera, gdy określony jest typ elementu, np. std::vector<int> .

Składnia

  • szablon < szablon-lista-parametrów > deklaracja
  • eksportuj szablon < lista-parametrów-lista > deklaracja / * do C ++ 11 * /
  • szablon <> deklaracja
  • deklaracja szablonu
  • deklaracja szablonu zewnętrznego / * od C ++ 11 * /
  • szablon < lista-parametrów-listy > klasa ... ( opt ) identyfikator ( opt )
  • template < szablon-lista-parametrów > identyfikator klasy ( opt ) = wyrażenie-id
  • szablon < lista-parametrów-listy > typename ... ( opt ) identyfikator ( opt ) / * od C ++ 17 * /
  • szablon < lista-parametrów-listy > identyfikator nazwy typu ( opt ) = wyrażenie-id / * od C ++ 17 * /
  • wyrażenie postfiksowe . wyrażenie id szablonu
  • wyrażenie postfix -> wyrażenie id szablonu
  • nested-name-specifier template simple-template-id ::

Uwagi

template słowa jest słowem kluczowym o pięciu różnych znaczeniach w języku C ++, w zależności od kontekstu.

  1. Po liście parametrów szablonu zawartej w <> deklaruje szablon, taki jak szablon klasy, szablon funkcji lub częściowa specjalizacja istniejącego szablonu.

    template <class T>
    void increment(T& x) { ++x; }
    
  2. Po pustym <> deklaruje wyraźną (pełną) specjalizację .

    template <class T>
    void print(T x);
    
    template <> // <-- keyword used in this sense here
    void print(const char* s) {
        // output the content of the string
        printf("%s\n", s);
    }
    
  3. Po której następuje deklaracja bez <> , tworzy wyraźną deklarację lub definicję instancji .

    template <class T>
    std::set<T> make_singleton(T x) { return std::set<T>(x); }
    
    template std::set<int> make_singleton(int x); // <-- keyword used in this sense here
    
  4. W ramach listy parametrów szablonu wprowadza parametr szablonu szablonu .

    template <class T, template <class U> class Alloc>
    //                 ^^^^^^^^ keyword used in this sense here
    class List {
        struct Node {
            T value;
            Node* next;
        };
        Alloc<Node> allocator;
        Node* allocate_node() {
            return allocator.allocate(sizeof(T));
        }
        // ...
    };
    
  5. Po rozdzieleniu zakresu operator :: i operatorzy dostępu do klasy . i -> określa, że następująca nazwa jest szablonem.

    struct Allocator {
        template <class T>
        T* allocate();
    };
    
    template <class T, class Alloc>
    class List {
        struct Node {
            T value;
            Node* next;
        }
        Alloc allocator;
        Node* allocate_node() {
            // return allocator.allocate<Node>();       // error: < and > are interpreted as
                                                        // comparison operators
            return allocator.template allocate<Node>(); // ok; allocate is a template
            //               ^^^^^^^^ keyword used in this sense here
        }
    };
    

W wersjach wcześniejszych niż C ++ 11 szablon można było zadeklarować za pomocą słowa kluczowego export , co czyni go szablonem eksportowanym . Definicja wyeksportowanego szablonu nie musi być obecna w każdej jednostce tłumaczenia, w której szablon jest tworzony. Na przykład miało działać:

foo.h :

#ifndef FOO_H
#define FOO_H
export template <class T> T identity(T x);
#endif

foo.cpp :

#include "foo.h"
template <class T> T identity(T x) { return x; }

main.cpp :

#include "foo.h"
int main() {
    const int x = identity(42); // x is 42
}

Z powodu trudności w implementacji słowo kluczowe export nie było obsługiwane przez większość głównych kompilatorów. Został usunięty w C ++ 11; obecnie użycie słowa kluczowego export jest nielegalne. Zamiast tego zwykle konieczne jest zdefiniowanie szablonów w nagłówkach (w przeciwieństwie do funkcji innych niż szablony, które zwykle nie są zdefiniowane w nagłówkach). Zobacz Dlaczego szablony mogą być implementowane tylko w pliku nagłówkowym?

Szablony funkcji

Szablony można również zastosować do funkcji (a także bardziej tradycyjnych struktur) z tym samym efektem.

// 'T' stands for the unknown type
// Both of our arguments will be of the same type.
template<typename T>
void printSum(T add1, T add2)
{
    std::cout << (add1 + add2) << std::endl;
}

Można to następnie wykorzystać w taki sam sposób, jak szablony struktur.

printSum<int>(4, 5);
printSum<float>(4.5f, 8.9f);

W obu przypadkach argument szablonu służy do zastąpienia typów parametrów; wynik działa tak jak normalna funkcja C ++ (jeśli parametry nie pasują do typu szablonu, kompilator zastosuje standardowe konwersje).

Jedną dodatkową właściwością funkcji szablonów (w przeciwieństwie do klas szablonów) jest to, że kompilator może wywnioskować parametry szablonu na podstawie parametrów przekazanych do funkcji.

printSum(4, 5);     // Both parameters are int.
                    // This allows the compiler deduce that the type
                    // T is also int.

printSum(5.0, 4);   // In this case the parameters are two different types.
                    // The compiler is unable to deduce the type of T
                    // because there are contradictions. As a result
                    // this is a compile time error.

Ta funkcja pozwala nam uprościć kod, gdy łączymy struktury i funkcje szablonów. W standardowej bibliotece występuje wspólny wzorzec, który pozwala nam tworzyć template structure X za pomocą funkcji pomocniczej make_X() .

// The make_X pattern looks like this.
// 1) A template structure with 1 or more template types.
template<typename T1, typename T2>
struct MyPair
{
    T1      first;
    T2      second;
};
// 2) A make function that has a parameter type for
//    each template parameter in the template structure.
template<typename T1, typename T2>
MyPair<T1, T2> make_MyPair(T1 t1, T2 t2)
{
    return MyPair<T1, T2>{t1, t2};
}

Jak to pomaga?

auto val1 = MyPair<int, float>{5, 8.7};     // Create object explicitly defining the types
auto val2 = make_MyPair(5, 8.7);            // Create object using the types of the paramters.
                                            // In this code both val1 and val2 are the same
                                            // type.

Uwaga: Nie ma to na celu skrócenia kodu. Ma to na celu zwiększenie niezawodności kodu. Umożliwia zmianę typów poprzez zmianę kodu w jednym miejscu, a nie w wielu lokalizacjach.

Przekazywanie argumentów

Szablon może akceptować zarówno odniesienia do wartości, jak i wartości, przy użyciu odwołania do przesyłania :

template <typename T>
void f(T &&t);

W takim przypadku rzeczywisty typ t zostanie wydedukowany w zależności od kontekstu:

struct X { };

X x;
f(x); // calls f<X&>(x)
f(X()); // calls f<X>(x)

W pierwszym przypadku typ T jest wydedukowany jako odniesienie do X ( X& ), a typ t jest wartością odniesienia do wartości X , podczas gdy w drugim przypadku typ T jest dedukowany jako X a typ t jako wartość odniesienia do X ( X&& ).

Uwaga: Warto zauważyć, że w pierwszym przypadku decltype(t) jest taki sam jak T , ale nie w drugim.

Aby idealnie przekazać t do innej funkcji, bez względu na to, czy jest to odwołanie do wartości, czy do wartości, należy użyć std::forward :

template <typename T>
void f(T &&t) {
    g(std::forward<T>(t));
}

Referencyjne przekazywanie może być używane z różnymi szablonami:

template <typename... Args>
void f(Args&&... args) {
    g(std::forward<Args>(args)...);
}

Uwaga: Odwołania przekazujące mogą być używane tylko dla parametrów szablonu, na przykład w poniższym kodzie v jest odwołaniem do wartości, a nie odwołaniem do przekazywania:

#include <vector>

template <typename T>
void f(std::vector<T> &&v);

Szablon klasy podstawowej

Podstawową ideą szablonu klasy jest to, że parametr szablonu jest zastępowany typem w czasie kompilacji. W rezultacie ta sama klasa może być ponownie użyta dla wielu typów. Użytkownik określa, który typ zostanie użyty, gdy zadeklarowana zostanie zmienna klasy. Trzy przykłady tego pokazano w main() :

#include <iostream>
using std::cout;

template <typename T>         // A simple class to hold one number of any type
class Number {
public:
    void setNum(T n);         // Sets the class field to the given number
    T plus1() const;          // returns class field's "follower"
private:
    T num;                    // Class field
};

template <typename T>         // Set the class field to the given number
void Number<T>::setNum(T n) {
    num = n;
}

template <typename T>         // returns class field's "follower"
T Number<T>::plus1() const {
    return num + 1;
}

int main() {
    Number<int> anInt;        // Test with an integer (int replaces T in the class)
    anInt.setNum(1);
    cout << "My integer + 1 is " << anInt.plus1() << "\n";     // Prints 2

    Number<double> aDouble;   // Test with a double
    aDouble.setNum(3.1415926535897);
    cout << "My double + 1 is " << aDouble.plus1() << "\n";    // Prints 4.14159

    Number<float> aFloat;     // Test with a float
    aFloat.setNum(1.4);
    cout << "My float + 1 is " << aFloat.plus1() << "\n";      // Prints 2.4

    return 0;  // Successful completion
}

Specjalizacja szablonów

Możesz zdefiniować implementację dla określonych instancji klasy / metody szablonu.

Na przykład, jeśli masz:

template <typename T>
T sqrt(T t) { /* Some generic implementation */ }

Następnie możesz napisać:

template<>
int sqrt<int>(int i) { /* Highly optimized integer implementation */ }

Następnie użytkownik, który napisze sqrt(4.0) , otrzyma implementację ogólną, a sqrt(4) implementację specjalistyczną.

Częściowa specjalizacja szablonów

W przeciwieństwie do pełnej specjalizacji szablonów częściowa specjalizacja szablonów pozwala wprowadzić szablon z poprawionymi niektórymi argumentami istniejącego szablonu. Częściowa specjalizacja szablonów jest dostępna tylko dla klasy / struktur szablonów:

// Common case:
template<typename T, typename U>
struct S {
    T t_val;
    U u_val;
};

// Special case when the first template argument is fixed to int
template<typename V>
struct S<int, V> {
    double another_value;
    int foo(double arg) {// Do something}
};

Jak pokazano powyżej, częściowe specjalizacje szablonów mogą wprowadzać zupełnie różne zestawy danych i członków funkcji.

Po utworzeniu wystąpienia częściowo wyspecjalizowanego szablonu wybierana jest najbardziej odpowiednia specjalizacja. Na przykład zdefiniujmy szablon i dwie częściowe specjalizacje:

template<typename T, typename U, typename V>
struct S {
    static void foo() {
        std::cout << "General case\n";
    }
};

template<typename U, typename V>
struct S<int, U, V> {
    static void foo() {
        std::cout << "T = int\n";
    }
};

template<typename V>
struct S<int, double, V> {
    static void foo() {
        std::cout << "T = int, U = double\n";
    }
};

Teraz następujące połączenia:

S<std::string, int, double>::foo();
S<int, float, std::string>::foo();
S<int, double, std::string>::foo();

wydrukuje

General case
T = int
T = int, U = double

Szablony funkcji mogą być w pełni wyspecjalizowane:

template<typename T, typename U>
void foo(T t, U u) {
    std::cout << "General case: " << t << " " << u << std::endl;
}

// OK.
template<>
void foo<int, int>(int a1, int a2) {
    std::cout << "Two ints: " << a1 << " " << a2 << std::endl;
}

void invoke_foo() {
    foo(1, 2.1); // Prints "General case: 1 2.1"
    foo(1,2);    // Prints "Two ints: 1 2"
}

// Compilation error: partial function specialization is not allowed.
template<typename U>
void foo<std::string, U>(std::string t, U u) {
    std::cout << "General case: " << t << " " << u << std::endl;
}

Domyślna wartość parametru szablonu

Podobnie jak w przypadku argumentów funkcji, parametry szablonu mogą mieć swoje wartości domyślne. Wszystkie parametry szablonu z wartością domyślną należy zadeklarować na końcu listy parametrów szablonu. Podstawową ideą jest to, że parametry szablonu z wartością domyślną można pominąć podczas tworzenia szablonu.

Prosty przykład domyślnego użycia wartości parametru szablonu:

template <class T, size_t N = 10>
struct my_array {
    T arr[N];
};

int main() {
    /* Default parameter is ignored, N = 5 */
    my_array<int, 5> a;

    /* Print the length of a.arr: 5 */
    std::cout << sizeof(a.arr) / sizeof(int) << std::endl;

    /* Last parameter is omitted, N = 10 */
    my_array<int> b;

    /* Print the length of a.arr: 10 */
    std::cout << sizeof(b.arr) / sizeof(int) << std::endl;
}

Szablon aliasu

C ++ 11

Podstawowy przykład:

template<typename T> using pointer = T*;

Ta definicja czyni pointer<T> aliasem T* . Na przykład:

pointer<int> p = new int; // equivalent to: int* p = new int;

Szablony aliasów nie mogą być wyspecjalizowane. Funkcjonalność tę można jednak uzyskać pośrednio, odwołując się do typu zagnieżdżonego w strukturze:

template<typename T>
 struct nonconst_pointer_helper { typedef T* type; };

template<typename T>
 struct nonconst_pointer_helper<T const> { typedef T* type; };

template<typename T> using nonconst_pointer = nonconst_pointer_helper<T>::type;

Parametry szablonu szablonu

Czasami chcielibyśmy przekazać szablonowi typ szablonu bez ustalania jego wartości. Do tego są tworzone parametry szablonu szablonu. Bardzo proste przykłady parametrów szablonu:

template <class T>
struct Tag1 { };

template <class T>
struct Tag2 { };

template <template <class> class Tag>
struct IntTag {
   typedef Tag<int> type;
};

int main() {
   IntTag<Tag1>::type t;
}
C ++ 11
#include <vector>
#include <iostream>

template <class T, template <class...> class C, class U>
C<T> cast_all(const C<U> &c) {
   C<T> result(c.begin(), c.end());
   return result;
}

int main() {
   std::vector<float> vf = {1.2, 2.6, 3.7};
   auto vi = cast_all<int>(vf);
   for(auto &&i: vi) {
      std::cout << i << std::endl;
   }
}

Deklarowanie nietypowych argumentów szablonów za pomocą auto

W wersjach wcześniejszych niż C ++ 17 podczas pisania szablonu parametru nietypowego należy najpierw określić jego typ. Tak więc powszechnym wzorem stało się pisanie czegoś takiego:

template <class T, T N>
struct integral_constant {
    using type = T;
    static constexpr T value = N;
};

using five = integral_constant<int, 5>;

Jednak w przypadku skomplikowanych wyrażeń użycie czegoś takiego wymaga napisania decltype(expr), expr podczas tworzenia szablonów. Rozwiązaniem jest uproszczenie tego idiomu i po prostu zezwolenie na auto :

C ++ 17
template <auto N>
struct integral_constant {
    using type = decltype(N); 
    static constexpr type value = N;
};

using five = integral_constant<5>;

Opróżnij niestandardowy usuwacz dla unikalnego_ptr

Miłym motywującym przykładem może pochodzić z starając się łączyć pusty optymalizacji zasady z niestandardowym Deleter dla unique_ptr . Różne programy usuwające API C mają różne typy zwrotów, ale nas to nie obchodzi - chcemy tylko, aby coś działało dla dowolnej funkcji:

template <auto DeleteFn>
struct FunctionDeleter {
    template <class T>
    void operator()(T* ptr) const {
        DeleteFn(ptr);
    }
};

template <T, auto DeleteFn>
using unique_ptr_deleter = std::unique_ptr<T, FunctionDeleter<DeleteFn>>;

A teraz możesz po prostu użyć dowolnego wskaźnika funkcji, który może przyjąć argument typu T jako parametr nietypowy dla szablonu, niezależnie od typu zwracanego, i uzyskać z niego narzut o unique_ptr wielkości:

unique_ptr_deleter<std::FILE, std::fclose> p;

Nietypowy parametr szablonu

Oprócz typów jako parametru szablonu możemy deklarować wartości wyrażeń stałych spełniających jedno z następujących kryteriów:

  • typ całki lub wyliczenia,
  • wskaźnik do obiektu lub wskaźnik do funkcji,
  • odwołanie do wartości obiektu lub odwołanie do wartości funkcji,
  • wskaźnik do członka,
  • std::nullptr_t .

Podobnie jak wszystkie parametry szablonu, parametry szablonu nietypowego mogą być jawnie określone, domyślnie lub niejawnie wyprowadzone poprzez odliczenie argumentu szablonu.

Przykład użycia parametru innego niż typ szablonu:

#include <iostream>

template<typename T, std::size_t size>
std::size_t size_of(T (&anArray)[size])  // Pass array by reference. Requires.
{                                        // an exact size. We allow all sizes
    return size;                         // by using a template "size".
}

int main()
{
    char anArrayOfChar[15];
    std::cout << "anArrayOfChar: " << size_of(anArrayOfChar) << "\n";

    int  anArrayOfData[] = {1,2,3,4,5,6,7,8,9};
    std::cout << "anArrayOfData: " << size_of(anArrayOfData) << "\n";
}

Przykład jawnego określenia parametrów szablonów zarówno typów, jak i typów:

#include <array>
int main ()
{
    std::array<int, 5> foo; // int is a type parameter, 5 is non-type
}

Parametry szablonów innych niż typ są jednym ze sposobów osiągnięcia powtarzalności szablonu i umożliwiają wykonanie metaprogramowania .

Strukturalne szablony danych

C ++ 14

Często przydatne jest zdefiniowanie klas lub struktur, które mają zmienną liczbę i typ elementów danych, które są definiowane podczas kompilacji. Przykładem kanonicznym jest std::tuple , ale czasami konieczne jest zdefiniowanie własnych niestandardowych struktur. Oto przykład, który definiuje strukturę za pomocą łączenia (zamiast dziedziczenia jak w przypadku std::tuple . Zacznij od ogólnej (pustej) definicji, która służy również jako podstawowy przypadek zakończenia wycofania w późniejszej specjalizacji:

template<typename ... T>
struct DataStructure {};

To pozwala nam już zdefiniować pustą strukturę DataStructure<> data , choć nie jest to jeszcze zbyt przydatne.

Następnie następuje specjalizacja przypadków rekurencyjnych:

template<typename T, typename ... Rest>
struct DataStructure<T, Rest ...>
{
    DataStructure(const T& first, const Rest& ... rest)
        : first(first)
        , rest(rest...)
    {}
    
    T first;                                
    DataStructure<Rest ... > rest;
};

To wystarcza nam teraz do tworzenia dowolnych struktur danych, takich jak DataStructure<int, float, std::string> data(1, 2.1, "hello") .

Więc co się dzieje? Po pierwsze, należy zauważyć, że jest to specjalizacja, której wymaganiem jest, aby istniał co najmniej jeden parametr szablonu variadic (mianowicie T powyżej), nie dbając jednak o konkretny skład paczki Rest . Wiedza, że T istnieje, pozwala first zdefiniować jego element danych. Reszta danych jest rekurencyjnie pakowana jako DataStructure<Rest ... > rest . Konstruktor inicjuje oba te elementy, w tym rekurencyjne wywołanie konstruktora do rest elementu.

Aby to lepiej zrozumieć, możemy przejść przez przykład: załóżmy, że masz deklarację DataStructure<int, float> data . Deklaracja najpierw pasuje do specjalizacji, dając strukturę z int first i DataStructure<float> rest danych DataStructure<float> rest . Definicja rest ponownie pasuje do tej specjalizacji, tworząc float first swój własny DataStructure<> rest float first i DataStructure<> rest elementy DataStructure<> rest . Wreszcie ta ostatnia rest pasuje do definicji podstawowej, tworząc pustą strukturę.

Możesz to zwizualizować w następujący sposób:

DataStructure<int, float>
   -> int first
   -> DataStructure<float> rest
         -> float first
         -> DataStructure<> rest
              -> (empty)

Teraz mamy strukturę danych, ale nie jest to jeszcze szczególnie przydatne, ponieważ nie możemy łatwo uzyskać dostępu do poszczególnych elementów danych (na przykład, aby uzyskać dostęp do ostatniego elementu DataStructure<int, float, std::string> data , musielibyśmy użyć data.rest.rest.first , który nie jest dokładnie przyjazny dla użytkownika). Dlatego dodajemy do niego metodę get (potrzebną tylko w specjalizacji, ponieważ w strukturze przypadku podstawowego nie ma danych do get ):

template<typename T, typename ... Rest>
struct DataStructure<T, Rest ...>
{
    ...
    template<size_t idx>
    auto get()
    {
        return GetHelper<idx, DataStructure<T,Rest...>>::get(*this);
    }
    ...
};

Jak widać, funkcja get member jest sama w sobie wzorowana - tym razem na indeksie członka, który jest potrzebny (więc użycie może być takie jak data.get<1>() , podobne do std::tuple ). Rzeczywista praca jest wykonywana przez funkcję statyczną w klasie pomocniczej GetHelper . Powodem, dla którego nie można określić wymaganą funkcjonalność bezpośrednio w DataStructure „s get dlatego (jak będziemy niedługo zobaczyć) musielibyśmy Specjalizujemy na idx - ale to nie jest możliwe, aby specjalizować funkcji składowej szablonu bez specjalizujący zawierającego klasę szablon. Zauważ też, że użycie auto -stylu w stylu C ++ 14 znacznie ułatwia nasze życie, ponieważ w przeciwnym razie potrzebowalibyśmy dość skomplikowanego wyrażenia dla typu zwrotu.

Przejdźmy do klasy pomocniczej. Tym razem potrzebujemy pustej deklaracji forward i dwóch specjalizacji. Najpierw deklaracja:

template<size_t idx, typename T>
struct GetHelper;

Teraz przypadek podstawowy (gdy idx==0 ). W takim przypadku zwracamy tylko first członka:

template<typename T, typename ... Rest>
struct GetHelper<0, DataStructure<T, Rest ... >>
{
    static T get(DataStructure<T, Rest...>& data)
    {
        return data.first;
    }
};

W przypadku rekurencyjnym zmniejszamy idx i wywołujemy GetHelper dla rest członków:

template<size_t idx, typename T, typename ... Rest>
struct GetHelper<idx, DataStructure<T, Rest ... >>
{
    static auto get(DataStructure<T, Rest...>& data)
    {
        return GetHelper<idx-1, DataStructure<Rest ...>>::get(data.rest);
    }
};

Aby przejrzeć przykład, załóżmy, że mamy DataStructure<int, float> data i potrzebujemy data.get<1>() . To wywołuje GetHelper<1, DataStructure<int, float>>::get(data) (2. specjalizacja), co z kolei wywołuje GetHelper<0, DataStructure<float>>::get(data.rest) , który w końcu zwraca (według 1. specjalizacji, ponieważ teraz idx wynosi 0) data.rest.first .

Więc to jest to! Oto cały działający kod, z pewnym przykładem użycia w funkcji main :

#include <iostream>

template<size_t idx, typename T>
struct GetHelper;

template<typename ... T>
struct DataStructure
{
};

template<typename T, typename ... Rest>
struct DataStructure<T, Rest ...>
{
    DataStructure(const T& first, const Rest& ... rest)
        : first(first)
        , rest(rest...)
    {}
    
    T first;
    DataStructure<Rest ... > rest;
    
    template<size_t idx>
    auto get()
    {
        return GetHelper<idx, DataStructure<T,Rest...>>::get(*this);
    }
};

template<typename T, typename ... Rest>
struct GetHelper<0, DataStructure<T, Rest ... >>
{
    static T get(DataStructure<T, Rest...>& data)
    {
        return data.first;
    }
};

template<size_t idx, typename T, typename ... Rest>
struct GetHelper<idx, DataStructure<T, Rest ... >>
{
    static auto get(DataStructure<T, Rest...>& data)
    {
        return GetHelper<idx-1, DataStructure<Rest ...>>::get(data.rest);
    }
};

int main()
{
    DataStructure<int, float, std::string> data(1, 2.1, "Hello");
        
    std::cout << data.get<0>() << std::endl;
    std::cout << data.get<1>() << std::endl;
    std::cout << data.get<2>() << std::endl;
    
    return 0;
}

Jawna instancja

Wyraźna definicja instancji tworzy i deklaruje konkretną klasę, funkcję lub zmienną na podstawie szablonu, nie wykorzystując jej jeszcze. Do jawnej instancji można odwoływać się z innych jednostek tłumaczeniowych. Można tego użyć, aby uniknąć zdefiniowania szablonu w pliku nagłówkowym, jeśli zostanie utworzony tylko za pomocą skończonego zestawu argumentów. Na przykład:

// print_string.h
template <class T>
void print_string(const T* str);

// print_string.cpp
#include "print_string.h"
template void print_string(const char*);
template void print_string(const wchar_t*);

Ponieważ print_string<char> i print_string<wchar_t> są jawnie print_string.cpp w print_string.cpp , linker będzie mógł je znaleźć, nawet jeśli szablon print_string nie jest zdefiniowany w nagłówku. Gdyby te wyraźne deklaracje tworzenia nie były obecne, prawdopodobnie wystąpiłby błąd linkera. Zobacz Dlaczego szablony mogą być implementowane tylko w pliku nagłówkowym?

C ++ 11

Jeśli jawna definicja instancji jest poprzedzona słowem kluczowym extern , staje się jawną deklaracją instancji. Obecność wyraźnej deklaracji instancji dla danej specjalizacji zapobiega domyślnej instancji danej specjalizacji w bieżącej jednostce tłumaczeniowej. Zamiast tego odniesienie do tej specjalizacji, które w innym przypadku spowodowałoby domyślną instancję, może odnosić się do jawnej definicji instancji w tej samej lub innej JT.

foo.h

#ifndef FOO_H
#define FOO_H
template <class T> void foo(T x) {
    // complicated implementation
}
#endif

foo.cpp

#include "foo.h"
// explicit instantiation definitions for common cases
template void foo(int);
template void foo(double);

main.cpp

#include "foo.h"
// we already know foo.cpp has explicit instantiation definitions for these
extern template void foo(double);
int main() {
    foo(42);   // instantiates foo<int> here;
               // wasteful since foo.cpp provides an explicit instantiation already!
    foo(3.14); // does not instantiate foo<double> here;
               // uses instantiation of foo<double> in foo.cpp instead
}


Modified text is an extract of the original Stack Overflow Documentation
Licencjonowany na podstawie CC BY-SA 3.0
Nie związany z Stack Overflow