Freigeben über


Vorlagen (C++)

Vorlagen sind die Basis für die generische Programmierung in C++. Als stark typisierte Programmiersprache erfordert C++, dass alle Variablen einen bestimmten Typ aufweisen – entweder explizit vom Programmierer deklariert oder vom Compiler abgeleitet. Viele Datenstrukturen und Algorithmen sehen jedoch gleich aus (unabhängig davon, für welchen Typ sie verwendet werden). Mithilfe von Vorlagen können Sie die Vorgänge einer Klasse oder Funktion definieren und den Benutzer angeben lassen, für welche konkreten Typen diese Vorgänge verwendet werden sollen.

Definieren und Verwenden von Vorlagen

Eine Vorlage ist ein Konstrukt, das basierend auf Argumenten, die der Benutzer für die Vorlagenparameter bereitstellt, zur Kompilierungszeit einen normalen Typ oder eine normale Funktion generiert. 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 einzelnen Typparameter T, deren Rückgabewert und Aufrufparameter („lhs“ und „rhs“) alle von diesem Typ sind. Sie können einen Typparameter beliebig benennen, aber in der Regel werden einzelne Großbuchstaben verwendet. T ist ein Vorlagenparameter. Das Schlüsselwort typename sagt aus, dass dieser Parameter ein Platzhalter für einen Typ ist. Wenn die Funktion aufgerufen wird, ersetzt der Compiler jede Instanz von T durch das konkrete Typargument, das entweder vom Benutzer angegeben oder vom Compiler abgeleitet wird. Der Prozess, in dem der Compiler eine Klasse oder Funktion auf der Grundlage einer Vorlage generiert, wird als Vorlageninstanziierung bezeichnet. minimum<int> ist eine Instanziierung der Vorlage minimum<T>.

An anderer Stelle kann ein Benutzer eine Instanz der Vorlage deklarieren, die auf eine Ganzzahl spezialisiert ist. Angenommen, „get_a()“ und „get_b()“ sind Funktionen, die eine Ganzzahl zurückgeben:

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

Da es sich herbei jedoch um eine Funktionsvorlage handelt und der Compiler den Typ von T anhand der Argumente a und b ableiten kann, kann sie wie eine normale Funktion aufgerufen werden:

int i = minimum(a, b);

Wenn der Compiler diese letzte Anweisung erreicht, wird eine neue Funktion generiert, in der jedes Vorkommen von T in der Vorlage durch int ersetzt wird:

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

Die Regeln für die Typableitung durch den Compiler in Funktionsvorlagen basieren auf den Regeln für normale Funktionen. Weitere Informationen finden Sie unter Überladungsauflösung von Funktionsvorlagenaufrufen.

Typparameter

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

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

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

Das Schlüsselwort class ist in diesem Fall äquivalent zu typename. Das vorherige Beispiel kann wie folgt ausgedrückt werden:

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 beliebige integrierte oder benutzerdefinierte Typ kann als Typargument verwendet werden. Sie können beispielsweise std::vector in der Standardbibliothek verwenden, um Variablen vom Typ int, double, std::string, MyClass, const MyClass*, 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. Angenommen, Sie rufen z. B. minimum mit MyClass auf, wie in diesem Beispiel gezeigt:

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
}

In diesem Fall wird ein Compilerfehler generiert, da MyClass keine Überladung für den <-Operator bereitstellt.

Die Typargumente für eine bestimmte Vorlage müssen nicht zwingend alle zur gleichen Objekthierarchie gehören. Sie können aber eine Vorlage definieren, die eine solche Einschränkung erzwingt. Sie können objektorientierte Techniken mit Vorlagen kombinieren und so beispielsweise ein abgeleitetes Element in einem Element vom Typ „Vektor<Basis*>“ 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 Standardbibliothekscontainer für Elemente von T erzwingen, bestehen darin, dass T per Kopiervorgang zuweisbar und konstruierbar sein muss.

Typfremde Parameter

Im Gegensatz zu generischen Typen in anderen Programmiersprachen wie C# und Java unterstützen C++-Vorlagen typfremde Parameter (auch Wertparameter genannt). Sie können z. B. wie im folgenden Beispiel, das der std::array-Klasse in der Standardbibliothek ähnelt, einen konstanten ganzzahligen Wert bereitstellen, um die Länge eines Arrays anzugeben:

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 Kompilierzeit als Vorlagenargument übergeben und muss const oder ein constexpr-Ausdruck sein. Er wird wie folgt verwendet:

MyArray<MyClass*, 10> arr;

Andere Arten von Werten (einschließlich Zeigern und Verweisen) können als typfremde Parameter übergeben werden. Sie können beispielsweise einen Zeiger an eine Funktion oder an ein Funktionsobjekt übergeben, um einen Vorgang innerhalb des Vorlagencodes anzupassen.

Typableitung für typfremde Vorlagenparameter

Ab Visual Studio 2017 sowie ab dem /std:c++17-Modus leitet der Compiler den Typ eines typfremden Vorlagenarguments ab, das mit auto deklariert wird:

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, werden die zugehörigen Parameternamen nicht benötigt. Tatsächlich ist es ein Fehler, innerhalb des Texts von MyClass2 auf den Typnamen von Arr oder auf Klassenparameternamen zu 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 über Standardargumente verfügen. Wenn eine Vorlage über ein Standardargument verfügt, müssen Sie es bei der Verwendung nicht angeben. Die Vorlage „std::vector“ verfügt beispielsweise über ein Standardargument für die Zuweisung:

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 können:

vector<int> myInts;

Bei Bedarf können Sie aber auch wie folgt eine benutzerdefinierte Zuweisung 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 alle auf Standardwerte festgelegt sind, verwenden Sie leere spitze Klammern:

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

Vorlagenspezialisierung

Manchmal ist es nicht möglich oder wünschenswert, dass eine Vorlage genau den gleichen Code für einen beliebigen Typ definiert. Es kann beispielsweise Fälle geben, in denen Sie einen Codepfad definieren möchten, der nur ausgeführt werden soll, wenn das Typargument ein Zeiger, ein Element vom Typ „std::wstring“ oder ein Typ ist, der von einer bestimmten Basisklasse abgeleitet wird. 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. Für alle anderen Typen wird dagegen die allgemeinere Vorlage verwendet. Spezialisierungen, in denen alle Parameter spezialisiert werden, sind sogenannte vollständige Spezialisierungen. Wenn nur einige der Parameter spezialisiert sind, handelt es sich um eine teilweise Spezialisierung.

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 spezialisierte Typparameter eindeutig ist. Nur Klassenvorlagen können teilweise spezialisiert sein. Alle vollständigen und teilweisen Spezialisierungen einer Vorlage müssen im gleichen Namespace deklariert werden wie die ursprüngliche Vorlage.

Weitere Informationen finden Sie unter Vorlagenspezialisierung.