Templates, STL

Transcription

Templates, STL
26. 01. 2009
Kurs: Programmierung in C/C++
Programmierung in C/C++
Philipp Lucas
phlucas@cs.uni-sb.de
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