Template-Instanziierung
Template-Instanziierung ist die Erstellung einer konkreten Funktion oder einer konkreten Klasse von einem Funktions- oder Klassen-Template.
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
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 [1] 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ürint
und Zeile (2) die zugehörige Memberfunktionempty
fürdouble
. - (3) instanziiert explizit
MyClass
fürint
und Zeile (4) deren MemberfunktiongetType
fürdouble
. - (5) instanziiert explizit
isSmaller
für(int, int)
und Zeile (6) tut dasselbe für(double, double
) mit dem expliziten Template-Argumentdouble
.
Verstecken der Template-Implementierung
Wie kann die explizite Template-Instanziierung helfen, die Definition der Templates zu verstecken?
- Sollte Template-Deklaration in der Header-Datei formuliert sein.
- Die Template-Definition sollte in der Quelldatei sein die Instanziierung des Templates am Ende der Quelldatei stattfinden.
- 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.
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 ... . ( [2])
URL dieses Artikels:
https://www.heise.de/-6151298
Links in diesem Artikel:
[1] https://cppinsights.io/s/e8145723
[2] mailto:rainer@grimm-jaud.de
Copyright © 2021 Heise Medien