Templates, STL
Transcription
Templates, STL
26. 01. 2009 Kurs: Programmierung in C/C++ Programmierung in C/C++ Philipp Lucas [email protected] 26. 01. 2009 Philipp Lucas, CDL, UdS 1 26. 01. 2009 Kurs: Programmierung in C/C++ Heute ◮ Templates ◮ STL Philipp Lucas, CDL, UdS 2 26. 01. 2009 Kurs: Programmierung in C/C++ Grundlagen In der Übung: IntArray für sicheren Zugriff auf Array von int. Wie sähen CharArray, VoidPArray, UserDefinedClassArray aus? Genauso. Lösung: Generische Container: ◮ Sammlung von etwas ⊲ Welche Operationen sollen effizient sein? Einfügen, entfernen, Index-Zugriff,. . . ⊲ Wieviel Speicherplatz soll benötigt werden? ⊲ Wieviel Sicherheit sollen Operationen bieten? Typsicherheit, Zugriffssicherheit ◮ Implementierung: Je nach Möglichkeiten der Sprache Philipp Lucas, CDL, UdS 3 26. 01. 2009 Kurs: Programmierung in C/C++ Generische Container Generische Arrays etc.: ◮ Container mit gewissen Zugriffsfunktionen ◮ Garantie von (asymptotischen) Laufzeiten für bestimmte Operationen ◮ Sprachabhängige Implementierung: ⊲ Datenstruktur, die Object* enthält; Sprache stellt Typsicherheit beim Zugriff sicher → Prinzipiell nicht möglich in C++ ⊲ Datenstruktur, die void* enthält; Programmierer kümmert sich selbst um Typsicherheit (explizite Casts) → häufig verwendet in C-Containerbibliotheken ⊲ Datenstrukturen, die Objekte jeweils einen Typs enthalten; Typsicherheit direkt gegeben → Der Weg der C++-Standardbibliothek Philipp Lucas, CDL, UdS 4 26. 01. 2009 Kurs: Programmierung in C/C++ Generische Container in C++ Beispiel: Wachsendes Array mit gesichertem Zugriff: std::vector ◮ Arrays von int*, std::string, A*: std::vector<int*>, std::vector<std::string>, std::vector<A*> ◮ In einem Header (hier: <vector>) existiert ein Template (Schablone): Anweisung: Ein Array von einem Typ T sieht so aus:. . . ◮ Benutzung dieses Templates erzeugt Instanz für ein konkretes T ◮ Compiler erzeugt spezialisierten Code für die jeweilige Instanz ◮ Typüberprüfung und Spezialisierung also zur Compilezeit (OO-Polymorphismus findet zur Laufzeit statt.) Philipp Lucas, CDL, UdS 5 26. 01. 2009 Kurs: Programmierung in C/C++ Generische Container in C++ (2) Technisch: ◮ Definition (schematisch): template<typename T> class std::vector{ T* _array; size_t _size; public: vector(size_t init); /* Legt Feld bestimmter Groesse an. */ T& operator[](size_t i) const { return _array[i]; } size_t size() const { return _size; } }; ◮ Benutzung: std::vector<int> int array(16); int array[4]=-3; Philipp Lucas, CDL, UdS 6 26. 01. 2009 Kurs: Programmierung in C/C++ Generische Container in C++ (3) ◮ Compiler: Generiert automatisch Klasse class std::vector<int>{ int* _array; size_t _size; public: vector<int>(size_t init); /* Legt Feld bestimmter Groesse an. */ int& operator[](size_t i) const { return _array[i]; } size_t size() const { return _size; } }; ◮ Je eine Klasse für int-Arrays, std::string*-Arrays etc. ◮ Compiler generiert nur benutzten Code Philipp Lucas, CDL, UdS 7 26. 01. 2009 Kurs: Programmierung in C/C++ Templates ◮ Template hier: Parametrisierung einer Klassendefinition durch Typen ◮ Sinn: Herausfaktorisierung gleichen Codes. Unterschiede zu OO: ⊲ Klassenhierarchie: Gemeinsam genutzter Code einmal vorhanden in Oberklasse, kann von mehreren Unterklassen gemeinsam genutzt werden. ⊲ Templateinstanziierung: Gleicher Code mehrfach vorhanden, wird eigens pro Instanz neu spezialisiert erzeugt (Es gibt Mittel und Wege dagegen.) ◮ Templates allgemein: Parametrisierung einer Klassendefinition oder einer Funktion durch Typen oder Konstanten ◮ Templates heute: Einfache Templates von Funktionen, Container ◮ Container-Funktionalität der C++-Standardbibliothek: Standard Template Library (STL) Philipp Lucas, CDL, UdS 8 26. 01. 2009 Kurs: Programmierung in C/C++ Einfache Funktions-Templates Start: Funktion zum Vertauschen zweier Variableninhalte. ◮ Definition eines swap-Templates: template<class T> void swap(T& a, T& b){ T x=a; a=b, b=x; } ◮ Syntax: template<Templateparameter> Templatefunktion ◮ Templateparameter: Hier: Ein Typ, kenntlich durch class ⊲ Konkreter Typ muß keine Klasse sein (z. B. int) ⊲ typename statt class möglich, aber weniger gebräuchlich ◮ Innerhalb des Templates: Benutzung des Typparameters als Typ Philipp Lucas, CDL, UdS 9 26. 01. 2009 Kurs: Programmierung in C/C++ Benutzung einfacher Funktions-Templates template<class T> void swap(T& a, T& b){ T x=a; a=b, b=x; } ◮ Benutzung: ⊲ Implizite Instanziierung: swap(intvar1,intvar2); swap(Ap1,Ap2); ⊲ Compiler 1. schaut sich die Typen der Argumente an und stellt fest, welches T für die Templateinstanziierung verwendet werden muß; 2. generiert Code für die spezialisierte Funktion, sofern noch nicht da; 3. generiert Aufruf der spezialisierten Funktion. ◮ Es ist keine vorherige explizite Instanziierung notwendig. Philipp Lucas, CDL, UdS 10 26. 01. 2009 Kurs: Programmierung in C/C++ Benutzung einfacher Funktions-Templates (2) template<class T> T max(T a, T b){ return (a>b)?a:b; } Mögliche Probleme: ◮ max(floatvar,intvar): Es gibt kein passendes T ⊲ Explizite Instanziierung bei der Benutzung: max<float>(floatvar,intvar) ⊲ Anordnung, die Funktion T max(float a, float b) aufzurufen: Klappt nach automatischer Typumwandlung der intvar nach float ◮ max(pers struct1, pers struct2): Wenn kein operator> auf den Typen definiert ist: Fehler bei der Instanziierung Philipp Lucas, CDL, UdS 11 26. 01. 2009 Kurs: Programmierung in C/C++ Funktionstemplates: Sortierfunktion template<class T> void sort(T* array, unsigned int size){ bool changed; do { changed = false; for(unsigned int i=0; i<size-1; ++i) if(array[i]>array[i+1]) swap(array[i],array[i+1]), changed = true; } while(changed); } Vergleich mit C-Version: Eine Funktion mit Funktionszeiger für Vergleich, Tausch: ◮ Nachteil: Mehrfacher Code für gleiche Funktionalität ◮ Vorteil: Spezialisierte Vergleichs- und Tauschfunktionen Philipp Lucas, CDL, UdS 12 26. 01. 2009 Kurs: Programmierung in C/C++ Klassentemplates IDs eines Objektes: template<class T> class ID{ T _id; public: ID(const T& id) : _id(id) {} ID(const ID& id) : _id(id._id) {} virtual ~ID() {} ID& operator=(const ID& id) { _id = id._id; return this; } const T& id() const { return _id; } }; Philipp Lucas, CDL, UdS 13 26. 01. 2009 Kurs: Programmierung in C/C++ Klassentemplates (2) Benutzung: class Person : public ID<std::string>{ unsigned int _age; public: Person(const std::string& name, const unsigned int age) : ID<std::string>(name), _age(age) {} virtual ~Person() {} }; class Car : public ID<unsigned int>{ std::string _builder; static unsigned int _max_id; public: Car(const std::string& builder) : ID<unsigned int>(++_max_id), _builder(builder) {} virtual ~Car() {} }; Philipp Lucas, CDL, UdS 14 26. 01. 2009 Kurs: Programmierung in C/C++ Beispiel: Paare template<class First, class Right> class std::pair{ public: First first; Right second; pair() : first(First()), second(Second()) {} pair(const First& f, const Second& s) : first(f), second(s) {} }; std::pair<int,std::string> test(234,"test"); assert(test.first==234); Philipp Lucas, CDL, UdS 15 26. 01. 2009 Kurs: Programmierung in C/C++ STL Standard Template Library: Die Container-Klassen der C++-Standardbibliothek (obwohl die SL noch viel mehr auf T beruht) ◮ Listen: vector, list, deque, stack, queue, priority queue ◮ Mengen: set, multiset ◮ Tabellen: map, multimap Erhebliche Gemeinsamkeiten beim Zugriff: Iteratoren. Philipp Lucas, CDL, UdS 16 26. 01. 2009 Kurs: Programmierung in C/C++ vector #include <vector> ◮ Arrays, die automatisch wachsen (ähnlich IntArray) ◮ einige spezifische vector-Eigenschaften ◮ zahlreiche grundlegende Operationen wie auch bei anderen Containern std::vector<Car*> all cars; typedef std::vector<int> int v; Philipp Lucas, CDL, UdS 17 26. 01. 2009 Kurs: Programmierung in C/C++ Grundlegende Operationen template<class T> class std::vector{ ... void push_back(const T& element); // void pop_back(); // T& operator[](size_type where); // T& at(size_type where); // const T& operator[](size_type where) const T& at(size_type where) const; }; Philipp Lucas, CDL, UdS Am Schluss anfuegen. Letztes Element entfernen. Ungepruefter Zugriff. Gepruefter Zugriff. const; 18 26. 01. 2009 Kurs: Programmierung in C/C++ Grundlegende Operationen (2) template<class T> class std::vector{ ... std::vector(const std::vector<T>& right); std::vector<T>& operator=(const std::vector<T>& right); // Beide: Komplette Kopie des Inhaltes size_type size(); // Laenge. bool empty(); // Leerheitsabfrage. bool operator==(const std::vector<T>& right); // Elementweise. bool operator!=(const std::vector<T>& right); // Elementweise. bool operator<(const std::vector<T>& right); // Lexikographisch. }; Philipp Lucas, CDL, UdS 19 26. 01. 2009 Kurs: Programmierung in C/C++ Iteratoren Wichtiges generelles Konzept: Iteratoren zum Zugriff auf STL-Container. std::vector<int> intvec; std::vector<int>::iterator iter; iter = intvec.begin(); while(iter!=intvec.end()){ std::cout << "Element: " << *iter << ’.’ << std::endl; ++iter; } Philipp Lucas, CDL, UdS 20 26. 01. 2009 Kurs: Programmierung in C/C++ Iteratoren (2) Ein Iterator ist ein Objekt, welches auf etwas in einem Container zeigt. ◮ In der Regel nicht nur ein Zeiger auf den Datenwert (also hier: int*) ◮ T& operator*() zum Zugriff auf Element ◮ Überladung von operator->() ebenfalls (Ein Iterator ist trotzdem kein T*.) ◮ operator++(), operator--() zum Weiter- oder Zurückgehen ◮ operator==(), operator!=() zum Vergleich mit Iteratoren ◮ Spezielle Iteratoren: vector::begin() (erstes Element), vector::end() (direkt nach dem letzten Element) (also: *intvec.end() ist nicht gültig) ◮ Nicht unbedingt verfügbar: operator-(), operator<(), operator+=() etc. Philipp Lucas, CDL, UdS 21 26. 01. 2009 Kurs: Programmierung in C/C++ Arten von Iteratoren Vorwärts oder rückwärts: std::vector<T>::iterator: begin() end() ++ ++ ( ) [0] ++ i [1] [2] h −− ( ⊥ i −− −− std::vector<T>::reverse iterator: rbegin() rend() ++ [2] ) i ++ ( [1] [0] h −− Philipp Lucas, CDL, UdS ++ −− ( ⊥ i −− 22 26. 01. 2009 Kurs: Programmierung in C/C++ Arten von Iteratoren (2) Sind Veränderungen erlaubt oder nicht? ◮ std::vector<T>::const iterator, std::vector<T>::const reverse iterator: Keine Veränderungen der Werte erlaubt ◮ Betrifft Änderungen mit *: const T& std::vector<T>::const iterator::operator*(); ◮ Betrifft auch Änderungen wie das Löschen von Werten über den Iterator ◮ Iteration über const std::vector nur mit const-Varianten möglich ◮ Empfehlung: Wann immer möglich const iterator nutzen Philipp Lucas, CDL, UdS 23 26. 01. 2009 Kurs: Programmierung in C/C++ Löschen und Einfügen std::vector<T>::iterator std::vector<T>::insert(iterator iter, T element); std::vector<T>::iterator std::vector<T>::erase(iterator iter); ◮ insert: ⊲ Einsetzen vor dem Iterator (also codepush back(x) ist insert(end(),x)) ⊲ Iterator selbst wird ungültig ⊲ Allgemein: Kann Verschieben von Speicherplatz verursachen ⊲ Rückgabe: Iterator auf neues Element ◮ erase: ⊲ Löscht gezeigtes Element aus dem Container ⊲ Rückgabe: Iterator auf zuvor danach liegendes Element ⊲ Sonst: Siehe insert ◮ Niemals ungültig gewordene Iteratoren verwenden! Sicherheitsregel: Bei Manipulation von Listen nur einen Iterator verwenden Philipp Lucas, CDL, UdS 24 26. 01. 2009 Kurs: Programmierung in C/C++ Iteratoren in Templates Zur Manipulation von STL-Container verwendet man häufig Funktionen, die dann ihrerseites Templates sind: template<class T> void print_all(const std::vector<T>& vec){ for(typename std::vector<T>::const_iterator iter = vec.begin(); iter!=vec.end(); ++iter){ std::cout << *iter << ’ ’; } } typename nötig, um Compiler zu sagen, daß std::list<T>::const iterator ein Typ ist. Einfache Regel: typename vor allen internen templatisierten Typen. Philipp Lucas, CDL, UdS 25 26. 01. 2009 Kurs: Programmierung in C/C++ Beachtenswertes ◮ operator[](where) führt keine Prüfung durch: Operation nur definiert für where<size() (auch beim Schreiben) ◮ Token-Falle: >> ist ein Operator-Token: std::vector<std::vector<int>> ist syntaktisch falsch ◮ Kopiererei: Objektsemantik mit Inhaltskopien: ⊲ void print all(std::vector<int>);: Bei jedem Aufruf wird ein neuer Vektor mit dem kompletten, kopierten Inhalt des Operanden-Vektoren erzeugt (besser: print all(const std::vector<int>&) oder print all(const std::vector<int>*) ⊲ Klasse sorgt selbst für Freigabe des Inhalts-Arrays in ihrem Destruktor ◮ Iteratoren unterschiedlicher Vektoren: for(intvec::const iterator iter = v1.begin(); iter != v2.end(); ++iter) Philipp Lucas, CDL, UdS 26 26. 01. 2009 Kurs: Programmierung in C/C++ Typischer Fehler class A { std::vector<std::string> objects; public: std::vector<std::string> objects() const { return objects; } }; Philipp Lucas, CDL, UdS 27 26. 01. 2009 Kurs: Programmierung in C/C++ Typischer Fehler class A { std::vector<std::string> objects; public: std::vector<std::string> objects() const { return objects; } }; ◮ Ineffizienz: Neuanlegung und Kopie bei jedem Aufruf ◮ Problem bei Iteration: for(i = a.objects().begin(); i != a.objects().end(); ++i) Jeder Aufruf ist neuer Vektor! Philipp Lucas, CDL, UdS 27 26. 01. 2009 Kurs: Programmierung in C/C++ Typischer Fehler class A { std::vector<std::string> objects; public: const std::vector<std::string>& objects() const { return objects; } }; ◮ Arbeit auf dem eigentlichen Vektor, also keine Kopie und keine Probleme ◮ Übergabe als const&, also keine Überschreibung Philipp Lucas, CDL, UdS 27 26. 01. 2009 Kurs: Programmierung in C/C++ Typsicherheit class X : public Y { ...}; void print(Y*); void print(const std::vector<Y*>&); X* x; std::vector<X*>; print(x); // geht print(y); // geht nicht Philipp Lucas, CDL, UdS 28 26. 01. 2009 Kurs: Programmierung in C/C++ Typsicherheit class X : public Y { ...}; void print(Y*); void print(const std::vector<const X*>&); X* x; std::vector<X*>; print(x); // geht print(y); // geht immer noch nicht Philipp Lucas, CDL, UdS 28 26. 01. 2009 Kurs: Programmierung in C/C++ list #include <list> ◮ Ebenfalls sequentieller Container ◮ Implementierung als verkettete Liste: Kein operator[](), dafür push front, pop front ◮ Iteratoren, insert, erase wie bei vector ◮ size, clear ebenfalls verfügbar ◮ Objektsemantik wie bei Vektor (und bei anderen Containern): Kopie erzeugt neue Liste Stück für Stück Philipp Lucas, CDL, UdS 29 26. 01. 2009 Kurs: Programmierung in C/C++ vector/list Operationen: operator[] push back/pop back push front/pop front size vector O(1) O(1)∗ n. V. O(1) list n. V. O(1) O(1) O(1) Größe pro Element: Overhead bei list (Zeiger), nicht bei vector *: Manchmal Speicherallokation und -verschiebung noetig. Philipp Lucas, CDL, UdS 30 26. 01. 2009 Kurs: Programmierung in C/C++ Listenoperationen template<class T> class std::list{ ... void splice(iterator to_el, list& from); void splice(iterator to_el, list& from, iterator which); void splice(iterator to_el, list& from, iterator from_start, iterator from_end); // Verschieben (nicht Kopieren) von Elementen einer Liste in diese Liste. void sort(list&); // Sortiere Liste, Sortierung mit < void merge(list&); // Verschmelze sortierte Liste, Sortierung mit < void reverse(); void unique(); }; Philipp Lucas, CDL, UdS // Liste umdrehen. // Entfernt hintereinanderliegende Duplikate mit == 31 26. 01. 2009 Kurs: Programmierung in C/C++ set #include <set> ◮ Container ohne spezielle Reihenfolge ◮ Jedes Element kann nur einmal vorkommen ◮ Implementierung als Suchbaum: benötigt operator< auf Elementtyp ◮ Iteratoren, insert, erase wie üblich ◮ size, clear ebenfalls verfügbar ◮ Objektsemantik wie üblich Philipp Lucas, CDL, UdS 32 26. 01. 2009 Kurs: Programmierung in C/C++ Mengenoperationen template<class T> class std::list{ ... iterator find(const T&) const; // Finde Element, oder liefere end(). size_type count(const T&) const; // Gib Anzahl der Vorkommen zurueck. void insert(const T&); // Fuege Element ein. }; Philipp Lucas, CDL, UdS 33 26. 01. 2009 Kurs: Programmierung in C/C++ map #include <map> ◮ Hashtabelle: Speicher Schlüssel und Werte ◮ Kann jeden Schlüssel nur einmal enthalten ◮ Implementierung als Suchbaum: benötigt operator< auf Elementtyp ◮ operator[]() zum Einsetzen und Lesen von Werten ◮ Iteratoren, insert, erase wie üblich (aber mit Wert, siehe gleich) ◮ size, clear ebenfalls verfügbar ◮ Objektsemantik wie üblich Philipp Lucas, CDL, UdS 34 26. 01. 2009 Kurs: Programmierung in C/C++ Tabellenoperationen template<class Key, class Value> class std::map{ ... typedef std::pair<const Key,Value> value_type; void insert(const value_type&); iterator find(const Key&) const; // Finde Element, oder liefere end(). size_type count(const T&) const; // Gib Anzahl der Vorkommen zurueck. void insert(const value_type&); // Fuege Element ein. Value& operator[](const Key&); // Elementzugriff mit Schluessel // legt ggf. neues Element an. }; Philipp Lucas, CDL, UdS 35 26. 01. 2009 Kurs: Programmierung in C/C++ Iterieren über eine map Ist iter ein std::map<Key,Value>::iterator, so ist *iter ein std::pair<const Key,Value>. Diese Paar ist std::map<Key,Value>::value type. for(std::map<std::string, Person*>::iterator iter = personen.begin(); iter!=personen.end(); ++iter){ std::map<std::string, Person*>::value_type found = *iter; // value_type ist std::pair<const std::string, Person*>. std::cout << found.first << ’:’ << found.second->name();; iter->second = NULL; // Setzt in der Map den Wert, der bei iter->first eingetragen ist. } Gleicher Paar-Typ beim Einfügen mittels insert. Philipp Lucas, CDL, UdS 36 26. 01. 2009 Kurs: Programmierung in C/C++ Beachtenswertes ◮ insert verändert nicht das Gespeicherte, wenn der Schlüssel schon drin ist: std::map<int,int> map; map[4]=2; map.insert(std::pair<int,int>(4,3)); // Immer noch map[4]==2 ◮ operator[] verändert das Gespeicherte, wenn der Schlüssel nicht drin ist: std::map<const char*,Person*> map; if(map["Hans Mustermann"]!=NULL) std::cout << "Gefunden!"; // Jetzt ist "Hans Mustermann" ist der map mit Wert NULL ◮ Daher: Kein Zugriff mit operator[] auf const std::map: Nur Zugriff über find() und den Iterator möglich Philipp Lucas, CDL, UdS 37 26. 01. 2009 Kurs: Programmierung in C/C++ Sonstige Container ◮ Ein stack ist eine deque unter Umbenennung bestimmter Operationen (back ist top) und Weglassung anderer (operator[]). ◮ Eine queue ist eine deque ohne bestimmte Operationen (pop back). ◮ Eine priority queue ist eine als sortierter vector implementierte sortierte Liste. ◮ Ein multiset ist eine Menge, die Duplikate enthalten darf. ◮ Eine multimap Tabelle, bei der ein Schlüssel mehrere Einträge haben darf. Allgemein: ◮ Implementierungen teilweise als Templateparameter angebbar ◮ nicht behandelt in dieser Vorlesung Philipp Lucas, CDL, UdS 38 26. 01. 2009 Kurs: Programmierung in C/C++ Das ist nicht die ganze Wahrheit ◮ Bisherige Beispiele: Nur Inhaltstypen als Template-Parameter ◮ Fehlermeldungen zeigen: Tatsächliche Templates haben mehr Parameter ◮ Mehr Klassen der Standardbibliothek ◮ Vereinfachungen bei Iteratoren, bei Konstruktoren, Operationen. . . ◮ Ziel: ⊲ Sie sollen sichere Implementierungen üblicher Datenstrukturen kennen ⊲ Sie sollen Vorteile und Nachteile kennen ⊲ Sie sollen die üblichen Operationen mit diesen Strukturen kennen Philipp Lucas, CDL, UdS 39 26. 01. 2009 Kurs: Programmierung in C/C++ Ausblick ◮ Exceptions ◮ Namespaces ◮ Diverses Philipp Lucas, CDL, UdS 40