Programmieren - C++ Templates Reiner Nitsch 8471 reiner.nitsch@h-da.de
Was sind Funktionstemplates? C++ unterscheidet zwischen Funktionstemplates (dieses Kapitel) und Klassentemplates (später). Funktionstemplates sind Funktionen, die außer einer Werteparameter-Liste in ()-Klammern zur Werteübergabe eine Typparameterliste in <>-Klammern zur Typübergabe haben. So wie zur Laufzeit beim Funktionsaufruf die Aktual-Parameter an Stelle der in der Deklaration benannten Formal-Parameter eingesetzt werden, werden bei Funktions-Templates vom Compiler zur Compilezeit die Aktual-Typen an Stelle der deklarierten Formal-Typparameter eingesetzt. C++ Templates ermöglichen generische Programmierung. Das ist Programmierung unabhängig vom speziellen Objekt-Typ Motivation für Funktionstemplates: Vertauschen von int-, string- oder Fraction-Objekten void swap( int& a, int& b ) int help(a); a = b; b = help; void swap( Fraction& a, Fraction& b ) Fraction help(a); a = b; b = help; Algorithmus ist unabhängig vom Datentyp generischer Algorithmus! Welche Ansprüche stellt der Algorithmus an den Typ 'Fraction'? (Anforderungsprofil) 1) assignable 2) copy-constructable 01.06.2012 C++ Funktions-Templates 2
Funktion swap als Funktionstemplate Deklariert Funktion als Template, das vom Compiler erst bei Bedarf übersetzt wird. Alias T kann auch einfacher Datentyp sein! Auch mehrere Typparameter sind möglich Statt typename kann man auch class schreiben void swap( int& a, int& b ) int help(a); a = b; b = help; Anwendung des Templates swap void main() int a=5, b=6; swap( a, b ); double x=2.0, y=3.0; swap( x, y ); swap( a, x ); swap(a,int(x)); jetzt ok! Compiler erzeugt swap mit Ausprägung int, indem er jedes T durch int ersetzt! template < typename T > void swap( T& a, T& b ) Achtung! T help(a); Copy-Konstruktor Problem wenn T ein und a = b; Zuweisung Klassenobjekt müssen ist? b = help; für Typ T definiert sein! 01.06.2012 C++ Funktions-Templates 3 Typ-Aliase dürfen keine Referenzen oder Pointer sein Compiler erkennt, dass hier T durch int ersetzt werden muss und kompiliert swap(int,int) in Maschinencode. Compiler erkennt, dass hier T durch double ersetzt werden muss und übersetzt swap(double,double) in Maschinencode. Fehler: Deklaration von swap verlangt 2 Parameter gleichen Typs (Kein impliziter Type-Cast bei Templates!)..
Funktionstemplates Zusammenfassung und Einschränkungen Ein Template erlaubt dort EINEN generischen Algorithmus, wo sonst MEHRERE Überladungen desselben Algorithmus notwendig wären. Mit den Templates unterstützt C++ so das KISS-Prinzip. In Funktionstemplates erhält jeder austauschbare Typ einen Zweitnamen (Alias- Name). Sie werden in der Typparameterliste "< >" als solche gekennzeichnet. Bei jedem Aufruf einer Templatefunktion bestimmt der Compiler zunächst durch Deduktion automatisch, welche Typen an den Positionen der austauschbaren Typen aktuell stehen und übersetzt dann den so geänderten Quellcode für diese Ausprägung. Anstelle der automatischen Bestimmung der aktuellen Typausprägung durch den Compiler, kann bzw. muss der Programmierer die Ausprägung auch explizit vorgeben: long a=2, b=3; swap( a,b ); swap<>( a,b ); swap<long>(a,b); template<typename T> T f() //Definition f<int>(); //Aufruf mit expliziter Compiler bestimmt Typausprägung implizit durch Deduktion aus den Typen der Parameter beim Aufruf. Der Programmierer bestimmt hier explizit die gewünschte Typausprägung; eine vom Typ der Parameter abweichende Ausprägung kann nicht erzwungen werden Einschränkung: Typparametrisierte return-werte müssen explizit angegeben werden, weil der Compiler sie nicht deduzieren kann. Aufruf hier nur mit expliziter Ausprägung möglich. Ausprägung für return-type int 01.06.2012 C++ Zeiger 4
Templates: Schnittstelle und Implementierung Bisher galt: Die Schnittstelle einer Klasse oder Bibliothek gehört in eine Header- Datei, die Implementierung in eine cpp-datei. Dieses Prinzip der Trennung von Schnittstelle gilt auch weiterhin. Template-Implementierung muss inkludiert werden! Erklärung: Ein Funktionstemplate ist lediglich eine Bauanleitung für die Schnittstelle und die Implementierung eines Algorithmus. Übersetzt der Compiler eine Projektdatei, die diesen Algorithmus mit einer neuen Typausprägung nutzt, so muss dem Compiler auch die Implementierung des Algorithmus zur erneuten Übersetzung zur Verfügung stehen. Deshalb: Die Implementierung eines Templates gehört in die Headerdatei! // Datei: PG1Lib.h template < typename T > void swap( T& a, T& b ) T help(a); a = b; b = help; 01.06.2012 C++ Zeiger 5
Noch ein Beispiel Mischen von Objekten Generische Funktion zum Erzeugen einer Permutation einer Folge wird gebraucht, wenn der Zufall bei der Programmausführung eine Rolle spielen soll, um gegebene Objekte in eine zufällige Reihenfolge zu bringen. Beispiele: pfirst Zufällige Wiedergabe von Musiktiteln (shuffle mode) Mischen eines Skatspiels plast input void random_shuffle( int* pfirst, int* plast ) int count = plast-pfirst; for( int* pnext=pfirst; pnext!=plast; ++pnext ) ::swap( *pnext, *(pfirst + rand()%count) ); Callback template< typename T > void random_shuffle( T* pfirst, T* plast ) int count = plast-pfirst; for( int* pnext=pfirst; pnext!=plast; ++pnext ) ::swap( *pnext, *(pfirst + rand()%count) ); Implementierung für Ganzzahlfolge (int) Implementierung für bel. Objekttypen. Anforderungen an Objekttypen? 01.06.2012 C++ Zeiger 6
C++ Klassen-Templates Werden auch "parametrisierte Datentypen" genannt Werden oft benutzt, um typanpassbare Container zu realisieren (z.b. in STL) Beispiel: Ein Objekt soll 2 Werte speichern, einen int und einen char Wert class Pair_int_char public: int first; char second; Pair_int_char ( int x, char y ) : first(x), second(y) ; // Beispiel-Anwendung Pair_int_char pair1( 13, 'a' ); cout << pair1.first << endl; cout << pair1.second << endl; Ein anderes Objekt soll ebenfalls 2 Werte speichern, einen bool und einen double Wert class Pair_bool_double public: bool first; double second; Pair_bool_double ( bool x, double y ) : first(x), second(y) ; // Beispiel-Anwendung Pair_bool_double pair2( true, 1.1 ); cout << pair2.first << endl; cout << pair2.second << endl; Mögliche Anwendungen von Containerklassen für 2 Werte: rationale Zahlen, Punkte in einer Ebene, Geldbeträge, Das Beispiel läßt den Wunsch aufkommen, die Klasse pair generisch, d.h. unabhängig von den speziellen Typen zu programmieren und die Typen (ähnlich wie Funktions-Parameter) als Typ- Parameter zu übergeben. 01.06.2012 C++ Templates 7
Realisierung der Pair-Container als Klassen-Template template < typename T1, typename T2 > class Pair public: T1 first; T2 second; Pair( T1 f, T2 s ) first=f; second=s; ; template < typename T1, typename T2 > Pair<T1,T2>::Pair( T1 f, T2 s ) first=f; second=s; Anwendung: Diese Syntax erklärt T1 und T2 zu Typparametern Welche Anforderungen stellt diese Klasse an die Typen T1 und T2? Methoden/Kontruktor-Definition in der Deklaration (Inline-Implementierung) Kritik: Zuerst wird für first und second automatisch der Std-Konstruktor aufgerufen, dann die Objektzustände per Zuweisung korrigiert. Bei Methoden/Kontruktor-Definition außerhalb der Deklaration MÜSSEN zusätzlich zum Klassennamen die Typparameter angegeben werden. Klassentemplates müssen mit expliziter Ausprägung instantiiert werden Pair < int, char > pair3( 13, 'a' ); Pair < bool, double > pair4( true, 0.1 ); // Gleiche Datenstruktur wie pair1! // Gleiche Datenstruktur wie pair2! Auch benutzerdefinierte Typen können als Aktualtyp eingesetzt werden: Pair < Pair_int_char, float > pair7( pair1, 1.234 ); Pair < Pair<int,int>, float > pair8( pair6, 1.234 ); Pair < Fraction*, bool > pair9( &Fraction(2,3), true );
Optimierung der Klasse Pair Verbesserungspotential Der Pair-Konstruktor übernimmt Parameter "by value". Nachteil: Resourcenverschwendung, wenn große Objekte zu übergeben sind. Ausserdem fehlt der Standardkonstruktor. Weshalb? Der Copy-Konstruktor fehlt auch noch template < typename T1, typename T2 > class Pair public: T1 first; T2 second; ; Die STL stellt eine Klasse std::pair<typ1,typ2> mit gleicher Funktionalität bereit. Anwendung siehe Skript 12-1-PG2-STL-Student.pdf 01.06.2012 C++ Templates 9
Statische Containerklasse ArrayForFractions aus Übung 5 class ArrayForFraction public: enum MAX_SIZE = 128 ; private: Fraction elems[max_size]; public: size_t size() const; void fill ( const Fraction& val ); bool empty() const throw(); Fraction& operator[] ( size_t index ); Fraction& at( size_t index ); Fraction& front(); Fraction& back(); Fraction* data() throw(); ; 01.06.2012 C++ Zeiger 10
ArrayForFractions als Klassentemplate Schönheitsfehler: MAX_SIZE ist fest eingestellt! template <typename T, unsigned int MAX_SIZE> C++-Templates bieten auch dafür eine template <typename T> statische Lösung: Auch Werte class ArrayForFraction StaticArray integraler Typen können innerhalb der public: enum MAX_SIZE = 128 ; "<>"Klammern stehen. private: Fraction T elems[max_size]; public: size_t size() const; void fill ( const Fraction& T& val ); bool empty() const throw(); Fraction& T& operator[] ( size_t index ); Anwendung Fraction& T& at( size_t index ); Fraction& T& front(); int main() Fraction& T& back(); Array<Fraction,128> fractions; Array<double,10> doubles; Fraction* T* data() throw(); fractions[0] = Fraction(1,2); ; doubles[0] = 1.2; cout << fractions.size(); bool StaticArray<T,MAX_SIZE>::empty() cout << doubles.size(); return MAX_SIZE==0; return 0; 01.06.2012 C++ Zeiger 11
So, das war s erst mal! 01.06.2012 C++ Zeiger 12
Erzeugung von Zufallszahlen - int rand(void) rand() // gibt pseudo-zufällige Ganzzahl im Bereich [0,RAND_MAX] zurück // Bei wiederholtem Aufruf erhält man eine scheinbar zufällige // Sequenz von Ganzzahlen mit Gleichverteilung. Eine Pseudozufallszahl ist eine Zahl, die zufällig erscheint, in Wirklichkeit jedoch berechenbar ist. Konsequenz: Nach jedem Neustart einer Anwendung liefert rand() die gleiche Sequenz von "Zufallszahlen". Um dieses Verhalten zu ändern, muss man dem Generator bei jedem Systemsstart einen neuen Anfangswert geben. Empfehlung: srand( Initialisiert time(null) den Generator )(default: verwenden, 1) um bei jedem Programmstart einen neuen Anfangswert zu setzen. time ist in <ctime> definiert und liefert die Sekunden seit 1.1.1970. Beispiele: // RAND_MAX (=32767) ist in <cstdlib> definiert rand(); rand(); // liefert 18467 41 srand(2); rand(); rand(); // liefert 29216 45 srand(1); rand(); rand(); // liefert wieder 18467 41 srand( time(null) ); rand() % 100; // liegt im Bereich [0,99] rand() % 100 + 1; // liegt m Bereich [1,100] rand() % 21-10; // liegt im Bereich? [-10,10] 01.06.2012 37
C-Array an Funktion- bzw. Methode übergeben #include <iostream> #include <ctime> #include <cstdlib> //für rand(); srand() using namespace std; double mean ( int array[], int max ); void main() srand(time(0)); const int MAX_N = 1000; int randomnumber[max_n]; for (int i=0; i< MAX_N; i++) randomnumber[i] = rand(); // oder int max = 5; randomnumber[i] = rand() % max; // oder int min = -4; randomnumber[i] = rand() % (max-min)+min; cout << mean(randomnumber, MAX_N) << endl; srand() initialisiert Zufallszahlengenerator; sonst erzeugt rand() nach jedem Programmstart die gleiche Zufallsfolge time(0) liefert Sekunden seit 1.1.1970 liefert gleichwahrscheinliche unsigned int Zufallszahlen im Zahlbereich [0 RAND_MAX] stdlib.h: RAND_MAX 0x7fff (32767) // Bereich [0...max) Die Arraygröße wird i.a. auch // Bereich [min...max) übergeben. Die Funktion kann diese nicht selbst bestimmen. Ein Array wird als Parameter an eine Funktion übergeben durch Angabe des Namens ohne [] 01.06.2012 38