Vorlagen (C++)

Vorlagen sind die Basis für die generische Programmierung in C++. Als stark typierte Sprache erfordert C++, dass alle Variablen einen bestimmten Typ aufweisen, entweder explizit vom Programmierer deklariert oder vom Compiler abgeleitet werden. Viele Datenstrukturen und Algorithmen sehen jedoch unabhängig vom Typ, auf dem sie arbeiten, gleich aus. Mithilfe von Vorlagen können Sie die Vorgänge einer Klasse oder Funktion definieren und dem Benutzer angeben, an welchen konkreten Typen diese Vorgänge funktionieren sollen.

Definieren und Verwenden von Vorlagen

Eine Vorlage ist ein Konstrukt, das zur Kompilierungszeit einen normalen Typ oder eine normale Funktion generiert, basierend auf Argumenten, die der Benutzer für die Vorlagenparameter bereitstellt. Sie können beispielsweise eine Funktionsvorlage wie folgt definieren:

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

Der obige Code beschreibt eine Vorlage für eine generische Funktion mit einem einzigen Typparameter T, deren Rückgabewert und Aufrufparameter (lhs und rhs) all diesen Typ sind. Sie können einen Typparameter beliebig benennen, aber in der Regel werden einzelne Großbuchstaben am häufigsten verwendet. T ist ein Vorlagenparameter; der typename Schlüsselwort (keyword) gibt an, dass dieser Parameter ein Platzhalter für einen Typ ist. Wenn die Funktion aufgerufen wird, ersetzt der Compiler jede Instanz des T konkreten Typarguments, das entweder vom Benutzer angegeben oder vom Compiler abgeleitet wird. Der Prozess, in dem der Compiler eine Klasse oder Funktion aus einer Vorlage generiert, wird als Vorlageninstanziierung bezeichnet; minimum<int> ist eine Instanziierung der Vorlageminimum<T>.

An anderer Stelle kann ein Benutzer eine Instanz der Vorlage deklarieren, die auf "int" spezialisiert ist. Gehen Sie davon aus, dass get_a() und get_b() Funktionen sind, die ein Int zurückgeben:

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

Da dies jedoch eine Funktionsvorlage ist und der Compiler den Typ der T Argumente a und b ableiten kann, können Sie ihn wie eine normale Funktion aufrufen:

int i = minimum(a, b);

Wenn der Compiler auf diese letzte Anweisung trifft, wird eine neue Funktion generiert, in der jedes Vorkommen von T in der Vorlage durch Folgendes ersetzt intwird:

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

Die Regeln für die Ausführung des Typabzugs durch den Compiler in Funktionsvorlagen basieren auf den Regeln für gewöhnliche Funktionen. Weitere Informationen finden Sie unter Überladungsauflösung von Funktionsvorlagenaufrufen.

Typparameter

Beachten Sie in der minimum obigen Vorlage, dass der Typparameter T erst qualifiziert ist, wenn er in den Funktionsaufrufparametern verwendet wird, wobei die Binde- und Verweisqualifizierer hinzugefügt werden.

Es gibt keinen praktischen Grenzwert für die Anzahl der Typparameter. Trennen Sie mehrere Parameter durch Kommas:

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

Die Schlüsselwort (keyword) class entspricht typename in diesem Kontext. Sie können das vorherige Beispiel folgendermaßen ausdrücken:

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

Sie können den Auslassungszeichenoperator (...) verwenden, um eine Vorlage zu definieren, die eine beliebige Anzahl von Null- oder mehr Typparametern akzeptiert:

template<typename... Arguments> class vtclass;

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

Jeder integrierte oder benutzerdefinierte Typ kann als Typargument verwendet werden. Sie können z. B. "std::vector" in der Standardbibliothek verwenden, um Variablen vom Typ int", double"std::string", MyClass"constMyClass*" MyClass&usw. zu speichern. Die primäre Einschränkung bei der Verwendung von Vorlagen besteht darin, dass ein Typargument alle Vorgänge unterstützen muss, die auf die Typparameter angewendet werden. Beispiel: Wenn wir die Verwendung MyClass wie in diesem Beispiel aufrufenminimum:

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
}

Ein Compilerfehler wird generiert, da MyClass keine Überladung für den < Operator bereitgestellt wird.

Es besteht keine inhärente Anforderung, dass die Typargumente für eine bestimmte Vorlage alle zur gleichen Objekthierarchie gehören, obwohl Sie eine Vorlage definieren können, die eine solche Einschränkung erzwingt. Sie können objektorientierte Techniken mit Vorlagen kombinieren; Sie können z. B. ein abgeleitetes* in einer Vektorbasis<*>speichern. Beachten Sie, dass die Argumente Zeiger sein müssen

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

Die grundlegenden Anforderungen, die std::vector und andere Standardbibliothekcontainer elemente T auferlegen, sind kopierbare T und kopierkonstruktierbare Elemente.

Parameter ohne Typ

Im Gegensatz zu generischen Typen in anderen Sprachen wie C# und Java unterstützen C++-Vorlagen parameter ohne Typ, auch als Wertparameter bezeichnet. Sie können z. B. einen konstanten integralen Wert angeben, um die Länge eines Arrays anzugeben, wie in diesem Beispiel, das der std::array-Klasse in der Standardbibliothek ähnelt:

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

Beachten Sie die Syntax in der Vorlagendeklaration. Der size_t Wert wird zur Kompilierungszeit als Vorlagenargument übergeben und muss oder ein constexpr Ausdruck seinconst. Sie verwenden es wie folgt:

MyArray<MyClass*, 10> arr;

Andere Arten von Werten, einschließlich Zeigern und Verweisen, können als Parameter ohne Typ übergeben werden. Sie können beispielsweise einen Zeiger an eine Funktion oder ein Funktionsobjekt übergeben, um einen Vorgang im Vorlagencode anzupassen.

Typabzug für Vorlagenparameter ohne Typ

In Visual Studio 2017 und höher und im /std:c++17 Modus oder höher leitet der Compiler den Typ eines Nicht-Typ-Vorlagenarguments ab, das mit 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

Vorlagen als Vorlagenparameter

Eine Vorlage kann ein Vorlagenparameter sein. In diesem Beispiel verfügt MyClass2 über zwei Vorlagenparameter: einen Typnamenparameter T und einen Vorlagenparameter 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
};

Da der Parameter Arr selbst keinen Text hat, sind seine Parameternamen nicht erforderlich. Tatsächlich ist es ein Fehler, auf den Namen des Typs oder klassenparameters von Arr innerhalb des Textkörpers MyClass2zu verweisen. Aus diesem Grund können die Typparameternamen von Arr weggelassen werden, wie in diesem Beispiel gezeigt:

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

Standardvorlagenargumente

Klassen- und Funktionsvorlagen können Standardargumente aufweisen. Wenn eine Vorlage über ein Standardargument verfügt, können Sie es nicht angeben, wenn Sie sie verwenden. Die Vorlage "std::vector" weist beispielsweise ein Standardargument für den Allocator auf:

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

In den meisten Fällen ist die Standardklasse std::allocator akzeptabel, sodass Sie einen Vektor wie folgt verwenden:

vector<int> myInts;

Bei Bedarf können Sie jedoch einen benutzerdefinierten Zuweisungsgeber wie folgt angeben:

vector<int, MyAllocator> ints;

Bei mehrfachen Vorlagenargumenten müssen alle Argumente nach dem ersten Standardargument Standardargumente haben.

Wenn Sie eine Vorlage verwenden, deren Parameter standardmäßig sind, verwenden Sie leere Winkelklammern:

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

Vorlagenspezialisierung

In einigen Fällen ist es nicht möglich oder wünschenswert, dass eine Vorlage genau denselben Code für jeden Typ definiert. Sie können z. B. einen Codepfad definieren, der nur ausgeführt werden soll, wenn das Typargument ein Zeiger ist, oder ein std::wstring oder ein typ, der von einer bestimmten Basisklasse abgeleitet ist. In solchen Fällen können Sie eine Spezialisierung der Vorlage für diesen bestimmten Typ definieren. Wenn ein Benutzer die Vorlage mit diesem Typ instanziiert, verwendet der Compiler die Spezialisierung, um die Klasse zu generieren, und für alle anderen Typen wählt der Compiler die allgemeinere Vorlage aus. Spezialisierungen, auf die alle Parameter spezialisiert sind, sind vollständige Spezialisierungen. Wenn nur einige der Parameter spezialisiert sind, wird sie als Teilspezialisierung bezeichnet.

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

Eine Vorlage kann eine beliebige Anzahl von Spezialisierungen aufweisen, solange jeder spezielle Typparameter eindeutig ist. Nur Klassenvorlagen können teilweise spezialisiert sein. Alle vollständigen und teilweisen Spezialisierungen einer Vorlage müssen im selben Namespace wie die ursprüngliche Vorlage deklariert werden.

Weitere Informationen finden Sie unter Vorlagenspezialisierung.