Programmierkurs C++ Templates & STL (1/2) Prof. Dr. Stefan Fischer Institut für Telematik, Universität zu Lübeck https://www.itm.uni-luebeck.de/people/fischer
#2 Templates Die wichtigsten objekt-orientierten Abstraktionen in C++ Daten-Abstraktion (Klassen) Beziehungen zwischen Klassen (Vererbung) Polymorphismus Fördern die (Wieder-)Verwendung von vorhandenen (kompilierten) Implementierungen Verwendung der selben Datentypen für alle Anwendungen Beispiel class Stack { Stack(int max_size = 10); ~Stack(); bool push(const int& value); bool pop(); int& top(); private: unsigned int size_; unsigned int elements_; int* stack_[]; };
#3 Templates Eine Möglichkeit der Wiederverwendung dieses Quellcodes für andere Objekte (z.b. vom Typ Car ) basiert auf Copy&Paste Kopiere den existieren Stack Umbenennung in CarStack Ersetzen aller Vorkommen von int mit Car Fehleranfällig und Arbeitsintensiv Beispiel class CarStack { CarStack(int max_size = 10); ~CarStack(); bool push(const Car& value); bool pop(); Car& top(); private: unsigned int size_; unsigned int elements_; Car* stack_[]; };
#4 Templates Erkenntnis: Viele Datenstrukturen (z.b. ein Stack) können implementiert werden, ohne zu wissen, welchen Datentyp sie beinhalten sollen T steht in diesem Fall für einen beliebigen Datentyp Beispiel class Stack { Stack(int max_size = 10); ~Stack(); bool push(const T& value); bool pop(); T& top(); private: unsigned int size_; unsigned int elements_; T* stack_[]; };
#5 Templates C++ bietet einen Mechanismus, um genau das umzusetzen Durch Templates kann der selbe Quellcode für unterschiedliche Datentypen verwendet werden Wird vom Compiler automatisch umgesetzt Schlüsselwort: typename (es kann auch class verwendet werden) Beispiel template<typename T> class Stack { Stack(int max_size = 10); ~Stack(); bool push(const T& value); bool pop(); T& top(); private: unsigned int size_; unsigned int elements_; (T*) stack_[]; };
#6 Templates Templates erlauben die Parametrisierung von Klassen und Funktionen zur Übersetzungszeit Ein Template definiert eine Familie von Klassen oder Funktionen Der Ausdruck Stack<int> erzeugt einen neuen Datentyp, bei welchem ein int statt des T steht Dieser Prozess wird als Instanziierung eines Template bezeichnet Beispiele für Parameter eines Templates Typen (standardisierte oder benutzer-definierte) Konstanten, die zur Übersetzungszeit bekannt sind (z.b. Integer, Pointer und Referenzen auf Statische Entities) Andere Templates
Template Funktionen Beispiel template<typename T> void swap(t& x, T& y) { T temp = x; x = y; y = temp; } Verwendung int i1 = 5; int i2 = 7; Implizit: swap(i1, i2); // Compiler erkennt Typen automatisch Explizit: swap<int>(i1, i2);// Manuelle/Explizite angebe des Typs #7
Template Klassen: Deklaration Definition (verwendet class oder typename) Verwendung K<int, double> k; template<typename X, typename Y> class K { X x;... }; template<class X, class Y> class K { X x;... }; #8
Template Klassen: Definition Methoden werden inline implementiert Wie bei jeder anderen inline Implementierung einer Methode Typ Y wird in diesem Beispiel nicht verwendet Beispiel template<typename X, typename Y> class K { X x; X& getx() { return x; } }; #9
#10 Template Klassen: Definition Trennung von Deklaration und Definition Implementierung muss im Header File erfolgen Nennung des Template als Präfix vor jeder Methode Beispiel template<typename X, typename Y> class K { X x; X& getx(); }; template<typename X, typename Y> X& K<X,Y>::getX() { return x; }
#11 Templates: Standard Parameter Genau wie Funktionen können auch Templates über (Standard-) Parameter verfügen Dieser werden zur Übersetzungszeit wie Konstanten behandelt Beispiel template<typename T, unsigned int size_ = 10> class Stack { T(/* Kein size parameter mehr!*/); ~T(); bool push(const T& value); bool pop(); T& top(); private: /* Kein size parameter mehr */ unsigned int elements_; T* stack_[size]; // Ermöglicht statische Speicheranforderung };
Datenstrukturen In vielen Sprachen hat praktisch jedes Programm seine eigenen Datentypen implementiert Probleme: viel unnötiger Code, Fehlerquelle, schwer auszutauschen, jedes Programm war einzigartig hohe Einarbeitungszeit Daher: Standardbibliothek, welche alle Standardtypen bereitstellt STL = Standard Template Library Basiert auf jahrzehntelanger Erfahrung im Design und der Implementierung von wiederverwendbaren Bibliotheken Referenzen http://www.sgi.com/tech/stl/ http://en.cppreference.com/w/cpp/container #12
#13 C++ STL C++ bietet eine sehr umfangreiche, zuverlässige und effiziente Standard-Bibliothek Beinhaltet die Standard-Bibliothek von C (und löst diese ab) Realisiert mittels Template Klassen und Funktionen Die STL basiert auf jahrzehntelanger Erfahrung im Design und der Implementierung von Wiederverwendbaren Bibliotheken Ziele der STL (genau wie die von C++) Einfachheit Effizienz Flexibilität Allgemeingültigkeit Bevor etwas selbst implementiert wird, zuerst in die STL schauen!
#14 C++ STL Die STL umfasst String (Zeichenketten) iostreams (Ein- und Ausgabedatenströme) Kontainer Kontainer sind Objekte, welche andere Objekte (Elemente) speichern und Methoden anbieten, um auf die Elemente mittels Iteratoren zuzugreifen Sequentielle und assoziative Kontainer Iterator Generalisierung von Pointern Algorithmen verwenden Iteratoren um auf Kontainer zuzugreifen Algorithmen Allgemeingültige Algorithmen, zur typ-sicheren Verarbeitung von Sequenzen beliebigen Typs verwendet werden können Beispiele: reverse, sort,
#15 C++ STL: Kontainer Warum mehrere Kontainer? Unterschiedliche Kontainer bieten ähnliche Dienste an Sie unterscheiden sich jedoch im Verhalten und in der Schnittstelle Die Zeit- und Platzkomplexität unterscheidet sich über die verschiedene Operationen Beispiele Ein Stack hat eine andere Schnittstelle als ein Vector Das Einfügen von Elementen am Ende eines Vectors ist sehr schnell Das Einfügen von Elementen in der Mitte eines Vectors ist sehr langsam Das Einfügen von Elementen in eine Verkettete Liste ist immer sehr schnell, egal an welcher Position
C++ STL: Kontainer und Iteratoren Kontainer haben (wo möglich) eine sehr ähnliche Schnittstelle Eine Iterator-Klasse, z.b. vector<int>::iterator Methoden zum Elementzugriff, z.b. front(), insert( ), resize( ), begin(), end(), Kontainer unterstützen geschachtelte Iteratoren an, um auf die Unterelemente zuzugreifen z.b., vector<int>::iterator, set<car>::iterator Zugriff auf Elemente in einem Kontainer erfolgt typischerweise über Iteratoren vector<int> v; vector<int>::iterator it = v.begin(); Zeigt auf das erste Element vector<int>::iterator sec = it+2; Drittes Element vector<int>::iterator e = v.end(); Zeigt hinter das letzte Element vector<int>::iterator l = v.end() -1; Zeigt auf das letzte Element (falls vorhanden) begin() begin()+2 end()-1 end() 1 3 55 2 7 8 4 1 #16
C++ STL: Vector Der einfachste Kontainer in der STL und häufig der effizienteste Vector ist ein sequentieller Kontainer Bietet wahlfreien Zugriff auf alle Elemente Einfügen und Löschen am Ende benötigt konstante Zeit Einfügen und Löschen an jeder anderen Position benötigt lineare Zeit Dynamische Länge, benötigter Speicher wird vom Vector selbständig verwaltet Wichtige Methoden push_back(t& t), pop_back() Einfügen/Löschen am Ende begin(), end() Iteratoren front(), back() Referenzen auf bestimmte Elemente size(), empty(), max_size() Anzahl der Elemente und maximale Anzahl Elemente operator[](size_type n) Zugriff wie bei einem Array, z.b. v[3] insert(iterator pos, const T& x) Füge x an Position pos ein iterator erase(iterator pos) Löscht das Element an der Position pos #17
C++ STL: Verwendung eines <vector> #18
Überlauf des aktuell reservierten Speichers Was passiert, wenn mehr Elemente in dem Vector abgelegt werden sollen, als seine Kapazität erlaubt? Der Vector passt seine Kapazität an, indem er 1) Einen neuen, größeren Speicherbereich reserviert (typischerweise doppelt so groß wie der vorherige) 2) Alle Elemente vom alten Speicherbereich in den neuen Speicherbereich kopiert (mittels des Kopierkonstruktors der Elemente) 3) Die alten Objekte löscht (Destruktor jedes Elements wird aufgerufen) 4) Den alten Speicherbereich wieder freigibt Dieser Prozess verursacht Overhead zur Laufzeit Wenn die Anzahl der Elemente bekannt ist, die in einem Vector gespeichert werden soll, kann die Effizient mittels reserve(size_type n) gesteigert werden Dies verhindert die kostspielige Anpassung der Kapazität, indem gleich die passende Menge Speicher für den Vector reserviert wird #19
#20 Verwendung von Iteratoren Vorsicht: Iterator muss genau einmal erhöht werden!
#21 Verwendung von Iteratoren Oft besser: Referenz auf nächstes Objekt einer Variable zuweisen
Kürzere Variante: for-schleife Iterationen über Container lassen sich mit der for-schleife prägnant und kurz schreiben Initialisierung: Zuweisung einer Iterator-Referenz Bedingung: Prüfen auf Iterator!= end() Inkrement #22
#23 Noch kürzere Variante: for_each-schleife Abkürzende Schreibweise der STL aus <algorithm> Für jedes Element, welches mittels des Iterators erreicht wird, wird eine Funktion aufgerufen
#24 For_each Umwandlung Beispiel: Umwandlung einer for-schleife in eine for_each-schleife