Template-Instanziierung

Template-Instanziierung ist die Erstellung einer konkreten Funktion oder einer konkreten Klasse von einem Funktions- oder Klassen-Template.

vorlesen Druckansicht 13 Kommentare lesen
Lesezeit: 7 Min.
Von
  • Rainer Grimm
Inhaltsverzeichnis

Template-Instanziierung ist das Erstellen einer konkreten Funktion oder einer konkreten Klasse von einem Funktions- oder Klassen-Template.

Der Prozess kann implizit erfolgen (vom Compiler generiert) oder explizit (vom Benutzer bereitgestellt) sein.

Für das Template benötigte Template-Argument generiert der Compiler automatisch. Manchmal ist es notwendig, die Template-Definitionen aus Header-Dateien zu entfernen oder die rechenintensive Template-Instanziierung zu minimieren. In diesem Fall hilft die explizite Instanziierung.

Implizite Instanziierung sollte der Default sein. Das bedeutet, dass der Compiler automatisch die konkrete Funktion oder Klasse fĂĽr die angegebenen Template-Argumente erzeugt. Im Allgemeinen leitet der Compiler die Template-Argumente aus den Funktionsargumenten ab. In C++17 kann der Compiler auch die Template-Argumente fĂĽr Klassen-Templates ableiten.

// implicitTemplateInstantiation.cpp

#include <iostream>
#include <string>
#include <vector>

template <typename T>
class MyClass{
public:
MyClass(T t) { }
std::string getType() const {
return typeid(T).name();
}
};

template<typename T>
bool isSmaller(T fir, T sec){
return fir < sec;
}

int main(){

std::cout << '\n';

std::cout << std::boolalpha;

std::vector vec{1, 2, 3, 4, 5}; // (1)
std::cout << "vec.size(): " << vec.size() << '\n';

MyClass myClass(5); // (2)
std::cout << "myClass.getType(): " << myClass.getType() << '\n';

std::cout << '\n';

std::cout << "isSmaller(5, 10): " << isSmaller(5, 10) << '\n'; // (3)
std::cout << "isSmaller<double>(5.5f, 6.5): "
<< isSmaller<double>(5.5f, 6.5) << '\n'; // (4)

std::cout << '\n';

}

(1) und (2) verwenden class template argument deductoin (CTAG). std::vector oder MyClass können ihren Typ aus ihren Konstruktorargumenten ableiten. (3) leitet auch ihr Template-Argument ab. In (4) hingegen wird das Template-Argument double explizit angegeben: isSmaller<double>(5.5f, 6.5).

Der Compiler erzeugt fĂĽr jede implizite Template-Instanziierung eine konkrete Funktion oder Klasse. C++Insights visualisiert diesen Prozess.

Dieser automatische Prozess ist sehr komfortabel, hat aber auch ein paar Nachteile.

  • Beim impliziten Instanziieren ist die Definition des Templates typischerweise in einer Header-Datei sichtbar. Nicht jeder möchte aber die Definition offenlegen.
  • Ist ein Template fĂĽr bestimmte Template-Argumente erforderlich, instanziiert sie der Compiler, wenn es noch nicht in Ăśbersetzungseinheit verfĂĽgbar ist. Eine Ăśbersetzungseinheit ist die Quelldatei nach der Verarbeitung durch den C-Präprozessor. Typischerweise entfernt der Linker alle ĂĽberflĂĽssigen Template-Instanziierungen und behält eine. Dies ist eine Verschwendung von Zeit und Speicher.

Beide Probleme können mit expliziter Template-Instanziierung gelöst werden.

Explizite Instanziierung kennt zwei Varianten in C++: Definition und Deklaration der expliziten Instanziierung.

  • Syntax der Definition der expliziten Instanziierung: template <template declaration>
  • Syntax der Deklaration der expliziten Instanziierung: extern template <template declaration

Beim Vergleich der Syntax , macht das SchlĂĽsselwort extern den feinen Unterschied.

Explizite Template-Instanziierung bedeutet, dass Entwickler die Instanziierung eines Template direkt anfordern wie in folgendem einfachen Beispiel:

// explicitTemplateInstantiation.cpp

#include <iostream>
#include <string>
#include <vector>

template <typename T>
class MyClass{
public:
MyClass(T t) { }
std::string getType() const {
return typeid(T).name();
}
};

template<typename T>
bool isSmaller(T fir, T sec){
return fir < sec;
}

template class std::vector<int>; // (1)
template bool std::vector<double>::empty() const; // (2)

template class MyClass<int>; // (3)
template std::string MyClass<double>::getType() const; // (4)

template bool isSmaller(int, int); // (5)
template bool isSmaller<double>(double, double); // (6)

int main(){

std::cout << '\n';

std::cout << std::boolalpha;

std::vector vec{1, 2, 3, 4, 5};
std::cout << "vec.size(): " << vec.size() << '\n';

MyClass myClass(5);
std::cout << "myClass.getType(): " << myClass.getType() << '\n';

std::cout << '\n';

std::cout << "isSmaller(5, 10): "
<< isSmaller(5,10) << '\n';
std::cout << "isSmaller<double>(5.5f, 6.5): "
<< isSmaller<double>(5.5f, 6.5) << '\n';

std::cout << '\n';

}

Der Bereich (1) bis (6) ist die am interessantesten. Dank des SchlĂĽsselworts template erfolgt eine explizite Template-Instanziierung.

  • (1) instanziiert explizit std::vector fĂĽr int und Zeile (2) die zugehörige Memberfunktion empty fĂĽr double.
  • (3) instanziiert explizit MyClass fĂĽr int und Zeile (4) deren Memberfunktion getType fĂĽr double.
  • (5) instanziiert explizit isSmaller fĂĽr (int, int) und Zeile (6) tut dasselbe fĂĽr (double, double) mit dem expliziten Template-Argument double.

Wie kann die explizite Template-Instanziierung helfen, die Definition der Templates zu verstecken?

  1. Sollte Template-Deklaration in der Header-Datei formuliert sein.
  2. Die Template-Definition sollte in der Quelldatei sein die Instanziierung des Templates am Ende der Quelldatei stattfinden.
  3. Schließlich lässt sich das Template lässt sich durch Einbinden der Header-Datei einbinden.

Hier sind drei Dateien, die diesen Prozess veranschaulichen.

  • Template-Deklaration
// MyClass.h

#include <typeinfo>
#include <string>

template <typename T>
class MyClass{
public:
MyClass(T t) { }
std::string getType() const;
};
  • Template-Definition und explizite Instanziierung fĂĽr int
// MyClass.cpp

#include "MyClass.h"

template <typename T>
std::string MyClass<T>::getType() const {
return typeid(T).name();
}

template class MyClass<int>;
  • Verwenden des Templates
// mainMyClass.cpp

#include "MyClass.h"
#include <iostream>


int main() {

std::cout << '\n';

MyClass myClass(5);
std::cout << "myClass.getType(): " << myClass.getType() << '\n';

/*
MyClass myClass2(5.5);
std::cout << "myClass2.getType(): " << myClass2.getType() << '\n';
*/

std::cout << '\n';

}

Das Kompilieren und AusfĂĽhren des Programms liefert das erwartete Ergebnis.

Wenn ich versuche, MyClass fĂĽr einen anderen Datentyp als int zu verwenden, bekomme ich einen Linker-Fehler. Folgende Fehlermeldung erhalte ich, wenn ich die auskommentierten Zeilen verwende.

Es ist keine Template-Instanziierung fĂĽr double verfĂĽgbar.

Beim Verwenden von MyClass<int> in verschiedenen Ăśbersetzungseinheiten wirft der Linker im Wesentlichen alle Template-Instanziierung auĂźer einer weg. Das ist eine Verschwendung von Rechenzeit. Wer das extern-SchlĂĽsselworts in C++11 verwendet, kann eine Definition der expliziten Instanziierung in eine Deklaration der expliziten Instanziierung transformieren:

template class MyClass<int>;        //  Definition der expliziten 
// Instanziierung
extern template class MyClass<int>; // Deklaration der expliziten
// Instanziierung

Die wichtigste Beobachtung ist, dass die zweite Zeile keine Template-Instanziierung auslöst. Das bedeutet, dass der Compiler nichts erzeugt, was der Linker gegebenenfalls wegwirft. Es gilt lediglich, sicherzustellen, dass eine Instanziierung von MyClass<int> für den Linker verfügbar ist, da sonst ein Linker-Fehle erscheint.

Nach diesem eher technischem Artikel, schreibe ich in meinem nächsten Artikel über variadische Templates ... . ()