Du bist hier: Startseite > Entwicklung (C/C++) > C++ Grundlagen > Generische Programmierung mit Templates

Generische Programmierung mit Templates

Generische Programmierung ist ein Verfahren zur Entwicklung wiederverwendbarer Software-Bibliotheken. Dabei werden Funktionen oder Klassen möglichst allgemein entworfen, um für unterschiedliche Datentypen und Datenstrukturen verwendet werden zu können.

Die Implementierung erfolgt bei einigen Programmiersprachen durch das Konzept generischer Typen bzw. Templates – so gestalten sich dynamische Programmiersprachen, bei denen sich der Typ einer Variable zur Laufzeit ändern darf, durch ihre verallgemeinerte Polymorphie generisch. Von Sprachen, die solche Mechanismen bieten, sagt man auch, dass sie Generik erlauben.

Wesentlich bei der generischen Programmierung ist, dass die Algorithmen nicht für einen bestimmten Datentyp geschrieben werden, sondern nur bestimmte Anforderungen an die Typen stellen. Das Prinzip wird auch parametrische Polymorphie genannt.

Funktions Templates

Im Beispiel werden wir jeweils eine Funktion für insgesammt 2 Datentypen (int und float) erstellen, die jeweils 2 Parameter erwarten und als Rückgabe den jeweils größeren Parameter zurück geben. Sowas ist keine große Sache zumal es diese Funktionen schon vorgefertigt gibt, dass Beispiel soll aber die Komfortabilität von Funktions Templates in C++ verdeutlichen.

Datei:  Quelldateien/main.cpp
  1. #include <iostream> // std::cout, std::endl, std::cin
  2. #include <stdlib.h> // EXIT_SUCCESS
  3.  
  4. using std::cout;
  5. using std::endl;
  6. using std::cin;
  7.  
  8. int getMax(int a, int b)
  9. {
  10. return ((a > b) ? a : b);
  11. }
  12.  
  13. float getMax(float a, float b)
  14. {
  15. return ((a > b) ? a : b);
  16. }
  17.  
  18. int main(void)
  19. {
  20. cout << getMax(3, 6) << endl; // Ruft die Funktion mit Datentyp int auf
  21. cout << getMax(3.555555f, 6.555555f) << endl; // Ruft die Funktion mit Datentyp float auf
  22.  
  23. cin.get();
  24. return EXIT_SUCCESS;
  25. }

Das Überladen der Funktionen bewirkt hier, dass der Compiler anhand der übergebenen Parameter automatisch die passende Funktion aufruft (implizite Instanzierung). Wenn man sich die 2 Funktionen mal genau anschaut, dann wird einem schnell bewusst, dass sie beide das gleiche machen und sich lediglich der Rückgabetyp und der Parametertyp ändern.

An dieser Stelle kann man das Paradigma Generische Programmierung anwenden. Es würde bewirken, dass ein Template für die Funktion erstellt wird, indem Platzhalter anstelle der Datentypen notiert werden. Demnach müsste die Funktion getMax() nur einmal deklariert werden, was eine Optimierung des Quellcodes mit sich bringt. Im Rumpf der Funktion muss natürlich drauf geachtet werden, dass der automatisch gewählte Datentyp vom Template mit dem Vergleichsoperator > verwendbar ist.

Datei:  Quelldateien/main.cpp
  1. #include <iostream> // std::cout, std::endl, std::cin
  2. #include <stdlib.h> // EXIT_SUCCESS
  3.  
  4. using std::cout;
  5. using std::endl;
  6. using std::cin;
  7.  
  8. template<typename T>
  9. T getMax(T a, T b)
  10. {
  11. return ((a > b) ? a : b);
  12. }
  13.  
  14. int main(void)
  15. {
  16. // Implizite Instanzierung
  17. cout << getMax(3, 6) << endl; // Ruft die Funktion mit Datentyp int auf
  18. cout << getMax(3.555555f, 6.555555f) << endl; // Ruft die Funktion mit Datentyp float auf
  19. //Explizite Instanzierung
  20. cout << getMax<int>(3.555555f, 6.555555f) << endl; // Ruft die Funktion mit Datentyp int auf
  21.  
  22. cin.get();
  23. return EXIT_SUCCESS;
  24. }

Ein Template wird mit dem Schlüsselwort template eingeleitet, gefolgt von einer Kommaseparierten Typenliste die mit < und > umschlossen ist. Anstelle von typename kann auch class verwendet werden. Das Schlüsselwort typename ist gleichwertig mit dem Schlüsselwort class, allerdings kann man die Verwendung von beiden wie folgt einteilen: typename wird verwendet, wenn ein built-in oder eine Klasse als Parameter kommen kann, class wird benutzt, wenn ausschließlich Klassen erwartet werden. Diese Einteilung dient nur der Übersichtlichkeit und hat sonst keine Auswirkungen.

Im Beispiel wird in Zeile 8 bis 12 das Funktions Template erstellt. Der dynamische Variablen Name für den Datentyp ist hier T (Könnte auch A oder B sein!). In Zeile 17 und 18 erfolgt ein Impliziter Aufruf, wobei der Compiler anhand der übergebenen Parameter automatisch die Funktion wählt. In Zeile 20 dagegen ist der Datentyp beider Funktionsparameter float, zwischen Funktionsname und Klammer wird aber ein <int> notiert welches bewirkt, dass die übergebenen Parameter in denn Datentyp int konvertiert werden. Diese Art von Aufruf nennt man Implizite Instanzierung!

Falls man mal ein Datentyp hat, der irgendwie nicht in das Template passt, z.B. weil die verwendeten Operatoren im Template nicht mit dem Datentyp funktionieren, hat man die Möglichkeit sein Funktions- Template zu spezialisieren. Spezialisieren kann man mit dem Überladen von Funktionen vergleichen, es wird im Template eine neue Funktion mit gleichem Namen aber anderem Datentyp angelegt. Wie sowas funktioniert, siehst du hier:

Datei:  Quelldateien/main.cpp
  1. #include <iostream> // std::cout, std::endl, std::cin
  2. #include <stdlib.h> // EXIT_SUCCESS
  3.  
  4. using std::cout;
  5. using std::endl;
  6. using std::cin;
  7.  
  8. template<typename T>
  9. T getMax(T a, T b)
  10. {
  11. return ((a > b) ? a : b);
  12. }
  13. template<>
  14. // Spezialisierte Implementierung für Datentyp std::string
  15. const char* getMax(const char* a, const char* b)
  16. {
  17. const char* ret = "Spezialisiertes Template!";
  18. return ret;
  19. }
  20.  
  21. int main(void)
  22. {
  23. // Implizite Instanzierung
  24. cout << getMax(3, 6) << endl; // Ruft die Funktion mit Datentyp int auf
  25. cout << getMax(3.555555f, 6.555555f) << endl; // Ruft die Funktion mit Datentyp float auf
  26. //Explizite Instanzierung
  27. cout << getMax<int>(3.555555f, 6.555555f) << endl; // Ruft die Funktion mit Datentyp int auf
  28. cout << getMax<const char*>("Hallo", "Welt") << endl; // Ruft die Funktion mit Datentyp const char auf
  29.  
  30. cin.get();
  31. return EXIT_SUCCESS;
  32. }

Eine spezialisierte Implementierung von einer Funktion in einem Template wird mit dem Schlüsselwort template<> eingeleitet. Es wird kein typename T dokumentiert, da der Datentyp nicht automatisch generiert werden soll. Darunter wird die Funkion mit dem neuen Datentyp notiert. Es gelten die gleichen Regeln wie beim normalen Funktions- Überladen. Beim Aufruf sollte dann auch wie in Zeile 28 auf explizite Instanzierung gesetzt werden!

Klassen Templates

Du ahnst es sicherlich schon, aber das ganze Spielchen kann man auch mit Klassen und Datenstrukturen treiben.

Datei:  Quelldateien/main.cpp
  1. #include <iostream> // std::cout, std::endl, std::cin
  2. #include <stdlib.h> // EXIT_SUCCESS
  3.  
  4. using std::cout;
  5. using std::endl;
  6. using std::cin;
  7.  
  8. template<typename T1, typename T2> // oder template<class T1, class T2>
  9. class cls
  10. {
  11. public:
  12. T1 element;
  13. const char* ausgabe;
  14. cls(T1 arg1, T2 arg2)
  15. {
  16. cout << "\nInstanziert u. Parameter in element gespeichert!" << endl;
  17. this->element = arg1;
  18. this->ausgabe = arg2;
  19. }
  20. T1 getElement(void)
  21. {
  22. return this->element;
  23. }
  24. };
  25.  
  26. int main(void)
  27. {
  28. cls <int, const char*> instanze1(20, "Erste mal gestartet");
  29. cout << instanze1.ausgabe << endl;
  30. cout << instanze1.getElement() << endl;
  31.  
  32. // Pointer Variante
  33. cls <const char*, const char*> *pInstanze = (new cls<const char*, const char*>("Pointer Variante!", "Zweite mal gestartet"));
  34. cout << pInstanze->ausgabe << endl;
  35. cout << pInstanze->getElement() << endl;
  36.  
  37. cin.get();
  38. return EXIT_SUCCESS;
  39. }

Zu beachten ist hier, dass in diesem Template 2 Typenames übertragen werden. Diese müssen natürlich beim Instanzieren der Klasse berücksichtigt werden (Zeile 28 und 33)!

Die Variablennamen der Typenames sollten ebenfalls gut bedacht sein. Ich habe die Erfahrung gemacht, dass Windows sowas wie template<typename DATA> ohne Probleme laufen lässt. Der Linux G++ Compiler hat damit allerdings ein Problem. Was aber läuft unter Linux ist T1, T2 usw...

Kommentare zu diesem Beitrag

Sie müssen angemeldet sein, um eine Nachricht zu erstellen. Anmelden »