Klassen-Templates

Wir wollen ja nicht immer alle Arbeit selbst machen, also bietet uns C++ eine Möglichkeit, den Compiler in bestimmten Fällen die Hauptarbeit machen zu lassen. Klassen-Templates sind genau für diese Fälle da: wir haben ein Problem, welches wir mit Hilfe einer Klasse lösen wollen, was aber für unterschiedliche Typen immer wieder gleich gelöst werden muss. Statt jedesmal eine neue Klasse zu schreiben, geben wir dem Compiler einmal eine Blaupause, aus der er sich dann bei Bedarf die passenden Klassen selbst erzeugen kann.

Video

Code

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
#include <string>
#include <iostream>
 
template <typename T> class Optional
{
  T mValue;
  bool mHasValue;
   
public:
   
  Optional() : mHasValue(false) {  }
   
  Optional(T const &v) : mValue(v), mHasValue(true) { }
   
  void set(T const &v)
  {
    mValue = v;
    mHasValue = true;
  }
   
  void clear()
  {
    mHasValue = false;
  }
   
  T const &get() const
  {
    if (!mHasValue) {
      std::cerr << "Vorsicht! Leere Variable ausgelesen!\n";
    }
    return mValue;
  }
   
  bool hasValue() const
  {
    return mHasValue;
  }
};
 
int main()
{
  Optional<std::string> o;
   
  std::cout << "Hat o im Moment einen Wert? " << o.hasValue() << "\n";
   
  o.set("Test");
   
  std::cout << "Hat o im Moment einen Wert? " << o.hasValue() << "\n";
  std::cout << "o = " << o.get() << "\n";
  std::cout << "i = " << i.get() << "\n";
   
  o.clear();
  std::cout << "Hat o im Moment einen Wert? " << o.hasValue() << "\n";
}

Erklärung

Klassen-Templates sind für sich genommen keine eigenständigen Klassen, d.h. sie definieren keine neuen Typen. Sie sind vielmehr eine Blaupause, nach der der Compiler für uns neue Klassen generieren kann. Zu diesem Zweck haben sie eine eigene Parameterliste, die aber statt Werten (wie bei einer Funktion) Typen enthält (Zeile 4, der Teil in < .. >). Die Template-Parameter können in der Definition des Templates anstelle von Typen verwendet werden und werden bei der Instanziierung des Templates zu einem konkreten Typ dann durch den eingesetzten Parameter ersetzt. Die Instanziierung des Templates ist im Beispiel oben in Zeile 43 zu sehen. Der Name des Templates wird wie ein Typ verwendet, aber zusätzlich kommt dahinter in spitzen Klammern noch die Liste der konkreten Parameter für diese Instanz (im Beispiel std::string). Der Compiler wird nun das Template hernehmen und eine neue Klasse generieren, wo jedes Vorkommen des Templateparamters T durch std::string ersetzt wird. Diese neue generierte Klasse wird dann zu Binärcode übersetzt (mit allen Tricks, die der Compiler so beherrscht, wie Typprüfung, Optimierung etc.).

Wenn wir wie im Video das Template nochmal für int instanziieren, dann generiert der Compiler eine zweite Klasse mit eben der Ersetzung durch int. Diese beiden generierten Klassen definieren dann völlig unterschiedliche Typen aus Sicht des Compilers und können nicht ineinander umgewandelt werden. Sobald die Template-Ersetzung stattgefunden hat, besteht zwischen den beiden Klassen für den Compiler nicht mehr Zusammenhang, als zwischen zwei Klassen, die wir zufällig ähnlich programmiert haben. Das ist bspw. ein Unterschied zu den Generics in Java, bei denen nur eine Variante der Klasse existiert und die Template-Parameter nur für die Typprüfung des Compilers interessant sind (und zur Laufzeit nichts mehr über diese Parameter bekannt ist). Durch diese Eigenart, aus den Templates wirklich neue Klassen zu generieren, kann C++ noch einige Tricks bieten, die wir uns in zukünftigen Videos noch anschauen werden.