Template-Instanziierung

Modernes C++ Rainer Grimm  –  13 Kommentare

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.

Template-Instanziierung

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

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).

Template-Instanziierung

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

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.
Verstecken der Template-Implementierung

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.

Template-Instanziierung

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.

Template-Instanziierung

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

Unterdrücken der Template-Instanziierung

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.

Was geht's weiter?

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