Generics in C++: warum sie nützlich sind und wie man sie verwendet
\n\nJeder Programmierer stößt früher oder später auf die Situation, eine Funktion schreiben zu müssen, die mit verschiedenen Datentypen arbeitet. Zum Beispiel eine Funktion zur Suche des maximalen Elements – sie sollte gleichermaßen gut mit int, double und string funktionieren. Ohne spezielle Mittel müsste man für jeden Typ eine separate Implementierung schreiben. Genau hier helfen Generics (generische Typen) oder, wie sie in C++ genannt werden, Vorlagen.
In C++ ist der Vorlagenmechanismus die Grundlage der generischen Programmierung. Er ermöglicht es, Code zu schreiben, der mit jedem Datentyp arbeitet, ohne dabei die Leistung zu opfern. In diesem Artikel werden wir uns ansehen, was Generics in C++ sind, warum sie nützlich sind und wie man sie in der Praxis einsetzt. Das Material richtet sich an Anfänger, daher werden wir uns vom Einfachen zum Komplexen bewegen.
\n\nWas sind Vorlagen (Generics) in C++?
\n\nEine Vorlage (template) ist ein Konstrukt, das es ermöglicht, eine Funktion oder Klasse zu definieren, die mit einem beliebigen Datentyp arbeitet. Anstelle eines konkreten Typs geben Sie einen Typparameter an (z. B. T), und der Compiler setzt beim Kompilieren den benötigten Typ ein. Dies wird als Instantiierung einer Vorlage bezeichnet.
Stellen Sie sich eine Keksform vor. Die Form ist eine, aber die Kekse können aus verschiedenen Teigen hergestellt werden: Mürbeteig, Schokoladenteig, Haferflockenteig. Die Vorlage ist die Form, und der Typ (int, double, string) ist der Teig. Sie beschreiben den Algorithmus einmal, und der Compiler erstellt automatisch Versionen für die benötigten Typen.
\n\nEs gibt zwei Arten von Vorlagen:
\n- \n
- Funktionsvorlagen – zum Erstellen generischer Funktionen. \n
- Klassenvorlagen – zum Erstellen generischer Klassen (z. B. Container). \n
Funktionsvorlagen: der erste Schritt zu generischem Code
\n\nBeginnen wir mit dem Einfachsten – einer Funktionsvorlage. Angenommen, wir brauchen eine Funktion, die den Maximalwert von zwei Werten zurückgibt. So sieht sie ohne Vorlagen aus:
\n\nint max_int(int a, int b) {\n return (a > b) ? a : b;\n}\n\ndouble max_double(double a, double b) {\n return (a > b) ? a : b;\n}\n\nDer Code wird dupliziert. Mit einer Vorlage wird dies elegant gelöst:
\n\n#include <iostream>\n#include <string>\n\ntemplate <typename T>\nT max_value(T a, T b) {\n return (a > b) ? a : b;\n}\n\nint main() {\n std::cout << max_value(10, 20) << std::endl; // 20\n std::cout << max_value(3.14, 2.71) << std::endl; // 3.14\n std::cout << max_value(std::string("apple"), \n std::string("banana")) << std::endl; // banana\n return 0;\n}\n\nDas Schlüsselwort template teilt dem Compiler mit, dass es sich um eine Vorlage handelt. <typename T> ist die Liste der Vorlagenparameter. T ist ein Platzhaltername für den Typ, der später eingesetzt wird. Anstelle von typename kann auch class verwendet werden – es gibt keinen Unterschied:
template <class T>\nT max_value(T a, T b) {\n return (a > b) ? a : b;\n}\n\nWenn Sie max_value(10, 20) aufrufen, erkennt der Compiler, dass die Argumente int sind, und erstellt automatisch eine Version der Funktion für int. Dieser Prozess wird als Typableitung bezeichnet. Bei Bedarf kann der Typ auch explizit angegeben werden:
std::cout << max_value<double>(5, 7.5) << std::endl; // 7.5\n\nWichtig: Eine Funktionsvorlage muss in einer Header-Datei (oder vor ihrer Verwendung) definiert werden, da der Compiler die vollständige Definition für die Instantiierung sehen muss.
\n\nKlassenvorlagen: universelle Container erstellen
\n\nKlassenvorlagen sind ein noch leistungsfähigeres Werkzeug. Das bekannteste Beispiel sind die Container aus der Standardbibliothek (STL): std::vector<int>, st