Oft unterscheiden sich Lösungen verschiedener Probleme nur in den Datentypen, auf denen sie arbeiten, nicht jedoch in ihrer Funktionalität. Zum Beispiel wird eine Klasse, die eine Warteschlange implementiert, immer Funktionen zum Anhängen und Entfernen von Objekten bereitstellen müssen. Die Implementierung dieser ist häufig nur vom zu speichernden Datentyp abhängig und kann daher gleich implementiert werden. Genauso ist der Ablauf einer Sortierroutine für unterschiedliche Datentypen immer gleich und beruht nur auf dem Vergleich und dem Vertauschen von zwei Elementen.
In C++ kann man mit einem Template die Funktionalität einmal definieren und dann die Datentypen einfach in die Implementierung einfügen.
Ein Template ist eine Variable, Klasse oder Funktion mit (noch) nicht definierten Datentypen. Die eigentlichen Datentypen werden erst bei der Instanziierung eines Objekts einer Templateklasse oder beim Aufruf einer Templatefunktion als Parameter übergeben. Erst beim Compilieren wird für jeden benutzten Datentyp der entsprechende Code generiert. Dafür muss die gesamte Template-Definition sichtbar sein. Also werden Templates ausschließlich in Headerdateien definiert.
Template-Deklarationen haben die folgende Syntax:
template<{parameter-list}> {declaration}Es wird entweder ein Funktions- oder ein Klassentemplate deklariert.
Ein einzelner Parameter kann ein Datentyp (typename T oder
class T) oder ein Integer-Wert (int N) sein.
Mehrere Parameter werden durch Kommata getrennt:
template<typename Key, typename Value> ...;
template<typename T, int Size> ...;
template<unsigned N, char M> ...;Parameter für Templates müssen zur Compile-Zeit bekannt sein. Daher führt folgendes Beispiel zu einem Fehler:
int n = 100;
// std::array<double, n> ds; // Fehler: n nicht zur Compile-Zeit bekanntWerte, die zur Compile-Zeit feststehen, können mit const
markiert werden:
const int n = 100;
std::array<double, n> ds; // Ok. n ist constant expressionFunktionentemplates werden immer dann sinnvoll eingesetzt, wenn Algorithmen unabhängig vom Datentyp auf bestimmte Eigenschaften dieser Datentypen zurückgeführt werden können.
Ein einfaches Beispiel ist eine Maximumsfunktion:
template<typename T>
T& max(T& left, T& right) {
return right < left ? left : right;
}Wie man sieht, entspricht diese Definition einer normalen
Funktionsdefinition. Nur die template-Zeile wurde
hinzugefügt und anstatt eines konkreten Datentyps (z.B.
double) wurde der Parameter T verwendet. Damit
dieses Template für einen Datentyp verwendet werden kann, muss der
Operator < zwischen zwei Objekten vom Typ T
definiert sein.
Ein Funktions-Template kann grundsätzlich für alle Variablentypen genutzt werden. Einzige Voraussetzung hierfür ist, dass für diesen Datentyp alle Operatoren definiert sind, die in der Funktion verwendet werden. Der Compiler erzeugt zur Übersetzungszeit den benötigten Code für alle Funktionsaufrufe. In diesem Fall ist die Funktion also mehrmals vorhanden.
Klassentemplates werden immer dann eingesetzt, wenn Datenstrukturen unabhängig vom Typ der gespeicherten Elemente dargestellt werden können und immer die gleichen Eigenschaften und Methoden anbieten. Alle abstrakten Datentypen (Listen, Bäume, Stack etc.) sind typische Beispiele. Für diese gibt es daher auch bereits vorgefertigten Templates in der Standardbibliothek (s. ).
Im folgenden Beispiel wird ein Klassen-Template Array
definiert. Es ist generisch über dem Datentyp T und der
Array-Größe N. Die Parameter können in der gesamten
Template-Definition als Datentyp bzw. Wert benutzt werden.
template<typename T, int N>
class Array {
private:
T p_array[N];
public:
T* begin() {
return p_array;
}
T* end() {
return p_array + N;
}
T& at(int i);
};Der Name einer Klasse, die aus solch einem Template generiert wird, ist der Name des Klassen-Templates gefolgt von den konkreten Parametern in spitzen Klammern. Template-Instanziierungen mit unterschiedlichen Parametern sind unterschiedliche Typen.
Array<int, 15> a;
Array<int, 100> b;
// Typen von a und b sind verschieden:
// a == b ist nicht definiert
// mit weiteren Typen
Array<float, 1> f;
struct S {};
Array<S, 20> f;Überall, wo in der Definition T und N
verwendet werden, ersetzt der Compiler sie durch den konkreten Typ bzw.
Wert. Das heißt, für ein Integer-Array der Länge 15 wird folgender Code
generiert:
template<>
class Array<int, 15> {
private:
int p_array[15];
public:
int* begin() {
return p_array;
}
int* end() {
return p_array + 15;
}
int& at(int i);
};Man kann die Implementierung der Methoden direkt innerhalb der
Klassendefinition durchführen (wie bei begin und
end) oder wie sonst getrennt als Templatefunktion. Bei der
Verwendung des Scope-Operators :: muss dem Compiler
mitgeteilt werden, dass es sich um ein Template und nicht um eine
fertige Klasse handelt. Dies geschieht durch das angehängte
Platzhalterelement in spitzen Klammern (<T>) an den
Templatedatentyp:
template<typename T, int N>
T* Array<T, N>::end() {
return p_array + N;
}