C++11 - Seminar Programmierung Aufbau Dr.sc.nat. Michael J.M. Wagner, New Elements - Wagner Tech UG
←
→
Transkription von Seiteninhalten
Wenn Ihr Browser die Seite nicht korrekt rendert, bitte, lesen Sie den Inhalt der Seite unten
C++11 - Seminar
Programmierung Aufbau
Dr.sc.nat. Michael J.M. Wagner, New Elements∗
Revision 228
∗
michael@wagnertech.deInhaltsverzeichnis 1 Einstieg in C++ 4 1.1 Ein- und Ausgabe von Dateien . . . . . . . . . . . . . . . . . . . . . . . . . . . 6 1.2 Objektorientierung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8 1.3 Vererbung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11 2 Neuerungen in der Kernsprache 14 2.1 Verschiedenes (Teil 1) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15 2.2 Objektdefinition . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16 2.3 Lambda-Funktionen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18 2.4 Move vs. Copy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20 2.5 Verschiedenes (Teil 2) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22 3 Templates 25 3.1 Funktionstemplates . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25 3.2 Klassentemplates . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26 3.3 Templates der Standardbibliothek . . . . . . . . . . . . . . . . . . . . . . . . . 26 4 C++11 Standardbibliothek 29 4.1 Neue Container . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29 4.2 Algorithmen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32 4.3 Neue Funktionalität . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32 5 Multithreading 38 6 Quellen 39
IT-Schulungen.com
Portfolio
IT-Schulungen.com ist eines der führenden, herstellerunabhängigen Seminarportale von
Schulungen rund um die Informationstechnologie (IT) und das IT-Management. Seit über 15
Jahren ist IT-Schulungen.com eine anerkannte Anlaufstelle für viele Unternehmen und
Behörden, wenn es um die Durchführung von DACH-weiten Schulungen geht.
• Applikationsserver / • ERP-Systeme • Open Source
Middleware • IT Management • Portale
• Business Intelligence • IT-Recht / Lizenzierung • SAP®
• Business-Skills und • ITIL • Security
Führung • Mobile • Serversysteme
• Cloud • Multimedia • Softwareentwicklung
• CRM • Office • Systemmanagement
• Datenbanken
• eBusiness
www.IT-Schulungen.com New Elements GmbH | IT-
Schulungen.com
Zertifizierungen & Partnerschaften
www.IT-Schulungen.com New Elements GmbH | IT-
Schulungen.com
3Abbildung 1: C++-Geschichte2
1 Einstieg in C++1
Als Urväter von C++ gelten Smalltalk, die erste objektorientierte Programmiersprache und C,
die Spache für die Systemprogrammierung. C++ ist daher eine objektorietieret Sprache, die auch
für die Systemprogrammierung geeignet ist.
Die Geschichte von C++ startet in den späten 70-ern. Mit dem Hype der Objektorietierung,
der nach und nach aufkam, hasst alles „objektorieniert” zu sein. Der erste Titel war daher 1979
„C with classes”. 1983 wurde der Template-Mechanismus hinzugefügt und das Ganze hieß nun
C++.
Während des Hype der Objektorietierung wurde die Ansicht vertreten, dass allein die Einführung
der Objektorientierung zu wiederverwendbarer und wartbarer Software führt. Neue nützlich Klas-
sen werden entwickelt und gemeinsam genutzt. Aber was passierte: Nützliche Klassen wurden
für dasselbe Problem immer wieder auf’s Neue entwickelt. In den 90-ern existierte damit eine
Vielzahl von Klassen beispielsweise zur Behandlung von Zeichenketten. Jeder Compiler-Hersteller
lieferte seine eigene string -Klasse. Und diese Klassen waren nicht kompatibel. Daher wurde ein
Standardisierungsprozess gestartet. Die Schritte der Standardisierung zeigt Abbildung 1.
• Annotated Reference Manual : Entwürfe der Standardisierung
• 1998: ANSI-C++
Dieser Standard enthält die wichtigen Klassenbibliotheken. Die meisten sind Template-
Klassen. Daher wird dieser Standard oft STL (standard template library ) genannt.
• 2003: Verbesserungen zu C++98
Wichtigste Neuerung: auto_ptr, eine erste Klasse zur sicheren Freispeicherverwaltung.
1
Wolf: Kap. 1
2
Grimm: S. 3
4• 2011: C++11
2011 gab es keinen Hype der Objektorietierung mehr. Functional programming hielt nun
Einzug in die Sprache. Was bedeutet dies? Functional programming vermeidet Schleifen.
Wenn ein Algorithmus auf eine Menge Daten angewendet werden soll, wird der Algorithmus
definiert und dem Compiler mitgeteilt, dass dieser Algorithmus auf jene Daten anzuwenden
sei. Dieser Ansatz ermöglicht dem Compiler eine implizite Parallelisierung. Für eine einfache
Formulierung von Algorithmen wurden die Lambda-Funktionen eingeführt. Eine Lambda-
Funktion ist eine ad hoc-Definition einer Funktion.
• 2014: C++14: Verbesserungen zu C++11
Die Lambda-Finktionen erhalten neue Eigenschaften. In Kombination mit dem auto-Schlüsselwort
können nun auch generische Lambda-Funktionen geschrieben werden.
• 2017: C++17: Verbesserungen zu C++11
Standardisierung der Parallelisierung
Ziele von C++ 113
• Für den Einsteiger: einfacher zu lernen
• Für den Profi: eine noch bessere Programmiersprache für die Systemprogrammierung
• Multiparadigmen-Progammiersprache
– prozedural, strukturiert (C)
– objektorientiert, generisch (C++98)
– funktional (C++11): Vermeidung von Schleifen, Zuweisungen, ermöglicht dem Com-
piler eine Parallelisierung
Das erste C++-Programm4
Ein erstes einfaches Programm ist in Abbildung 2 abgebildet. Bevor es an die Praxis geht, sollen
folgende Spachelemente besprochen werden:
• Blöcke
• Ein- Ausgabeströme
In der Sprache C++ gibt es keine Funktionen zur Ein- oder Ausgabe, die Bestandteile der
Sprache selbst sind. Stattdessen wird die einfache Ein- und Ausgabe über die (objektorien-
tierte) Streambibliothek definiert:
– std::cout: Standard-Ausgabestream
– std::cerr: Standard-Fehlerausgabestream
– std::cin: Standard-Eingabestream
3
Grimm: S. 7
4
Wolf: Kap. 2
5# include < iostream >
/*
* Hauptprogramm
*/
int main () {
std :: cout i ;
return 0;
}
Abbildung 2: Das erste Programm in C++
• Bezeichner: Variablen, Funktionen, Klassen
• Literale
• Kommentare
Aufgabe:
Erstellen Sie in Ihrer Entwicklungsumgebung ein neues Projekt und bringen Sie ein einfaches
„Hallo Welt”-Programm zum Laufen.
1.1 Ein- und Ausgabe von Dateien5
Zum Lesen und Schreiben von Dateien stehen die Klassen std::ifstream und std::ofstream zur
Verfügung.
Folgende Dateioperationen werden unterstützt:
• Öffnen von Dateien
# include < fstream >
std :: ofstream file01 (" testdatei001 . dat ");
std :: ifstream file02 (" testdatei002 . dat ");
if ( ! file02 ) {
std :: cerr• Schließen von Dateien: data01.close();
Im Allgemeinen ist es nicht nötig Dateien zu schließen, da dies mit dem Ende des Blocks
automatisch geschieht.
• Zeilenweises Lesen und Schreiben
# include < fstream >
using namespace std ;
ifstream rStream (" datei . txt ");
ofstream wStream (" out . txt ");
string line ;
while ( getline ( rStream , line )) {
wStream tokenize ( stringstream & ss , char token );
6
Wolf: Kap. 17.3.6
7Diese Funktion liest immer wieder vom stream ss bis zum nächsten Auftreten von token oder
zum nächsten Zeilenumbruch und setzt die gelesenen Stücke zu einem vector (Array) aus Zei-
chenketten zusammen.
char toChar ( const string & str );
wandelt eine Zeichenkette in einen Charakter, indem die Zeichenkette in einen stringstream
geschrieben wird und das erste Zeichen wieder zurückgelesen wird.
int toInt ( const string & str );
wandelt eine Zeichenkette in eine Ganzzahl, indem die Zeichenkette in einen stringstream ge-
schrieben wird und alle Ziffern vom stream in eine Integervariable zurückgelesen werden.
Aufgabe:
Nehmen die Dateien util.h/cpp in Ihr Projekt auf und übersetzen Sie diese.
1.2 Objektorientierung
Klassen7
Klassendefinition:8
class Klassenname {
// Auf Elemente kann nur innerhalb einer Klasse
// zugegriffen werden
private :
typ daten1 ;
typ daten2 ;
...
// Zugriff von aussen auf die Elemente m " oglich
public :
typ funktionsname1 ( parameter );
typ funktionsname2 ( parameter );
...
};
Ein Beispiel findet sich in listings/011/automat1/automat.h9
Die Definition der Klassenmethoden erfolgt in der zugehörigen cpp-Datei:
listings/011/automat1/automat.cpp10
Greifen Klassenmethoden auf Datenelemente der Instanz zu, kann dies mit einem this-> verdeut-
licht werden:
7
Wolf: Kap. 11
8
Wolf: S. 238.
9
Wolf: S. 239.
10
Wolf: S. 240.
8string Automat :: get_standort () const {
return this - > standort ;
}
void Automat :: set_standort ( const string & standort ) {
this - > standort = standort ;
}
Greifen Klassenmethoden nur lesend auf die Datenelemente der Instanz zu, kann dies mit const
versichert werden.
Instanzierung auf dem Stapel:
Klassenname Objekt ;
Klassenname Objekt {}; // C ++11
// Beispiel f " ur Klasse Automat :
Automat device01 ;
Automat device01 , device02 ;
Automat device01 {}; // C ++11
Automat device01 {} , device02 {}; // C ++11
Aufruf von Klassenmethoden (Punktoperator):
// Objekt der Klasse " Automat " erzeugen
Automat device {};
// Daten des Objektes " andern
device . set_geld (20000);
device . set_standort (" Augsburg , Fuggerweg 345");
// Inhalt des Objektes ausgeben
device . print ();
Die Instanzierung im Freispeicher erfolgt mit new:
Automat * aptr = new Automat (); // Instanzierung
aptr - > set_geld (20000); // Verwendung
delete aptr ; // Speicherfreigabe
Da das delete oft nicht korrekt implementiert wird, gibt es seit 2003 den auto_ptr, der mit
C++11 durch den unique_ptr ersetzt wurde.
# include < memory > // f " ur std :: unique_ptr
...
// Speicher f " ur ein neues Objekt anfordern
std :: unique_ptr < Automat > device_ptr ( new Automat {});
std :: unique_ptr < Automat > device_ptr = make_unique < Automat >(); // Alternative
auto device_ptr = make_unique < Automat >(); // Alternative mit make_unique / auto
device_ptr - > set_standort (" Mainz , Hauptstrasse 1");
device_ptr - > print ();
Weitere Funktionen von unique_ptr:
• *device_ptr: Das dereferenzierte Objekt
• device_ptr.get(): Der Zeiger selbst. Die Verantwortung bleibt aber beim device_ptr.
• device_ptr.release(): Der Zeiger selbst. Die Verantwortung geht auf den Empfänger über,
device_ptr selbst wird leer.
9• device_ptr.reset(p): device_ptr übernimmt den neuen Zeiger p. Falls bereits in device_ptr
ein Zeiger gespeichert war, wird das zugehörige Objekt gelöscht.
Ergänzen Sie die Bibliotheksanwendung
• Erstellen Sie eine Klasse Medium mit den entsprechenden Bestandteilen und Defaultwer-
ten. In den objektorientierten Sprachen werden Klassen üblicherweise in gleichnamige
Dateien (Medium.cpp/.h) gelegt.
• Schreiben Sie zu jedem Datenelement einen Getter (z.B. string getSignatur() const;)
• Ergänzen Sie das Modul Medienverwaltung um eine Funktion
int addMedium(const Medium& medium), die die bereits bestehende Methode verwendet.
• Ergänzen Sie das Hauptprogramm um die Instanzierung eines Mediums mit seinen
Defaultwerten auf dem Stapel und einen Aufruf dieser Methode.
Konstruktoren und Destruktoren
Konstruktoren sind Funktionen, die bei der Instanzierung einer Klasse, unabhängig davon, wo sie
instanziert wird, ablaufen. Sie dienen meist der Initialisierung. Konstruktoren können Parameter
aufnehmen. Standardmäßig steht der Default-Konstuktor (ohne Parameter) zur Verfügung. Dieser
wird aber ausgeblendet, sobald ein spezieller Konstruktor definiert wird. In C++11 lässt sich dieser
mit MyClass() = default; wieder einblenden.
Deklaration von Konstruktoren: [listings/011/automat2/automat.h]
Definition von Konstruktoren:
Klassenname :: Klassenname ( )
: data1 { wert } , data2 { wert } , dataN { wert } {
// Konstruktionsk " orper mit Anweisungen
}
Sollen, wie im gezeigten Beispiel, im Konstruktor keine expliziten Prüfungen durchgeführt werden,
wenn der Funktionskörper also leer ist, dann empfiehlt es sich, den Konstruktor inline in der Hea-
derdatei zu definieren. Dazu lässt sich seit C++11 auch die Konstruktor-Delegation verwedenden
[listings/011/automat2/delegation/automat.h].
Es gibt Klassenelemente mit speziellen Signaturen, die der Compiler in bestimmten Situatio-
nen heranzieht. Grundsätzlich gilt hier ein „alles oder nichts”. Ist spezieller Code zum Kopieren
oder Löschen eines Objekts nötig, müssen alle drei (mit C++11 fünf) Elemente berücksichtigt
werden:
• Kopierkonstruktor: Class(const Class&)
• Zuweisungsoperator: Class& operator=(const Class&)
• Destruktor: ~Class()
• Move-Konstruktor (C++11): Class(Class&&)
• Move-Zuweisung (C++11): Class& operator=(Class&&)
10In modernem C++-Code, der die Möglichkeiten der Standardlibrary nützt, sollte möglichst auf
die Verwendung dieser Sprachelemente verzichtet werden (rule of zero 11 ):
• Verwenden Sie für größere Datenmengen fertige Container der Standardbibliothek.
• Benutzen Sie die neuen klugen Zeiger.
Ergänzen Sie die Bibliotheksanwendung:
• Ergänzen Sie die Klasse Medium um einen Konstruktor, der die einzelnen Bestandteile
als Parameter übernimmt.
• Verwenden Sie im Hauptprogramm diesen Konstruktor.
1.3 Vererbung12
Motivation: Gemeinsamkeiten nur einmal implementieren: s. Abb. 3.
Abbildung 3: Vererbungsbeziehung13
Definition der Mutterklasse: [listings/014/vererbung001/person.h]
Ableitung von Kunde: [listings/014/vererbung001/kunde.h]
In Kunde wird das ”Mehr” des Kunden gegenüber der Person implementiert. Alle Eigenschaften
von Person erbt Kunde.
Wie zu sehen ist, enthalten sowohl Person als auch Kunde eine Funktion mit der Signatur
void print() const.
11
Wolf: S. 291f.
12
Wolf: Kap. 15
13
Wolf: S. 346
11Dies nennt man Überschreiben von Methoden. Auf die überschriebene Methode der Mutterklasse
kann direkt aus der Kindklasse zugegriffen werden:
void print () const {
std :: coutAufgabe:
Ergänzen Sie die Bibliotheksanwendung um die Dateien MediumHira.h/.cpp:
• Leiten Sie von der Klasse MediumBase die Klassen Buch und CD ab.
• Verteilen Sie die Attribute sinnvoll auf die Klassen.
• Ergänzen Sie jede Klasse um eine Methode string format() const, die den Datensatz
als Zeichenkette in dem Format zurückgibt, wie er in der Eingabedatei erwartet wird.
Für die Umwandlung von int in string gibt es die std::to_string-Funktion.
• Für die Klasse MediumBase implementieren Sie eine Dummy-Ausgabe.
• Testen Sie die format-Funktion für alle drei Klassen im Hauptprogramm.
Polymorphismus
Zur Motivation dieses Kapitels implementieren Sie diese Aufgabe:
Aufgabe:
Ergänzen Sie die Bibliotheksanwendung:
• Ergänzen Sie die Medienverwalatung um eine Funktion
int addMedium(const MediumBase& medium),
die die neue format()-Methode zum Schreiben in die Datei verwendet. Auf die Dupli-
katsprüfung kann erst mal verzichtet werden.
• Verwenden Sie die Methode im Hauptprogramm, indem Sie einmal ein Buch, einmal
eine CD übergeben.
Sie werden sehen, dass in der Datei nur immer der Dummy-Eintrag für ein Medium erscheint,
obwohl Sie im Hauptptogramm konkrete Instanzen implementiert haben. An dieser Stelle wird nun
ein Mechanismus benötigt, der zur Laufzeit die tatsächliche Klasse einer Instanz bestimmt und
danach die passende Implentierung auswählt. Dieser Mechanismus nennt sich Polymorphismus
(Vielgesichtigkeit).
Das polymorphe Überschreiben von Methoden erfolgt duch die modifier virtual in der Basisklasse
und override in den abgeleiteten Klassen [listings/014/virtual02/main.cpp].
Aufgabe:
Ergänzen Sie die Bibliotheksanwendung:
• Implementieren Sie die format()-Methode polymorph.
• Bringen Sie die neue addMedium-Methode zum Laufen.
13Abstrakte Klassen und Methoden
Die Dummy-Implementierung in MediumBase macht nicht viel Sinn, da ja Instanzen von MediumBase
auch nicht viel Sinn machen. MediumBase kapselt ja nur die Gemeinsamkeiten von Buch und CD.
Die Gemeinsamkeit bezüglich der format()-Methode ist, dass sie in den abgeleiteten Klassen
vorhanden ist. Die reine Existenz lässt sich mit einer Deklaration virtual string format() = 0;
beschreiben. Es wird dem Compiler gesagt, dass die abgeleiteten Klassen diese Methode im-
plementieren müssen. Als Konsequenz verhinder der Compiler, dass Instanten von MediumBase
gebildet werden. Die format()-Methode in MediumBase nennt sich abstrakte Methode.
Eine Klasse, die mindestens eine abstrakte Methode hat, nennt sich abstrakte Klasse 15 .
Aufgabe:
Ändern Sie MediumBase zu einer abstrakten Klasse ab.
Aufgabe zu unique_ptr:
• Ergänzen Sie die Definition der Klasse MediumBase um eine statische Methode
(static Medium* createMedium(signatur, autor, ...)), die eine Instanz von Buch/CD im
Freispeicher erzeugt. Innerhalb dieser Methode soll die Instanz in einem unique_ptr ge-
halten werden.
• Rufen Sie die Funktion im Hauptprogramm auf. Halten Sie dort die Instanz gleichfalls
in einem unique_ptr und übergeben Sie die Instanz an addMedium.
2 Neuerungen in der Kernsprache
Anmerkung: Wenn ein Codebeispiel mit Ihrem Compiler nicht funktioniert, können Sie den Wand-
box16 -Compiler verwenden.
15
Wolf: S. 364
16
https://wandbox.org/
142.1 Verschiedenes (Teil 1)
Range-basierte For-Schleife und Typableitung
Aus vielen modernen Programmiersprachen bekannt (foreach)
• für C-Array
• für Container (u.a. vector, map, initializer_list)
• for (typ var : container_var)
Der Compiler weiß an vielen Stellen, was für ein Typ verwendet werden muss.
• Typisierung mit auto
auto i = 5;
for (auto var : container_var)
• Typisierung mit decltype
int b ;
decltype ( b ) a ;
auto steht immer für den Wert-Typ (keine Referenz). decltype hingegen liefert auch Referenz-
typen. Mit C++14 steht das Konstrukt decltype(auto) zur Verfügung, das wie auto verwendet
wird und auch Referenztypen liefert:17
auto i = 5; // int
int & iref = i; // int &
auto c = iref ; // int
auto & d = i; // int &
decltype ( iref ) e = i; // int &
decltype ( auto ) f = iref ; // int &
Mit C++11 ist es damit sehr einfach geworden über diese Container zu iterieren.
for ( auto & mapIt : phonebook )
std :: coutnullptr
Bisher wurde NULL für einen Zeiger benutzt, der auf keinen gültigen Speicher zeigt. NULL ist aber
als Alias für die Zahl 0 definiert. Daher ist NULL vom Typ int. Mit C++11 war es das Ziel zwischen
Zeigern und Zahlen unterscheiden zu können. nullptr wurde daher im Sprachkern definiert und
ist vom Typ void*.
Automatischer Rückgabetyp
Bei der Deklaration/Definition von Funktionen kann auch das Schlüsselwort auto für den Typ
verwendet werden. In diesem Fall bestimmt das erste return-Statement den Rückgabetyp.19
auto Correct ( int i ) {
if ( i == 1)
return i ; // return type deduced as int
else
return Correct (i -1)+ i ; // ok to call it now
}
2.2 Objektdefinition
Diverse Neuerungen in der Klassendefinition
• Die Initialisierer-Liste: ein neuer Container.
– Verwendung in Konstruktoren
– Deklaration: MyClass(std::initializer_list data);
– Verwendung: MyClass myClass{ele1, ele2, ele3};
– Beispiel: [TourDeC++11/initializerListConstructor.cpp]20
• Delegation von Konstruktoren:
Mit der „:”-Syntax kann ein anderer Konstruktor aufgerufen werden:
.[TourDeC++11/delegatingConstructor.cpp]21
• Vererbung von Konstruktoren: using Base::Base;
.[TourDeC++11/inheritingConstructor.cpp]22
19
https://en.wikipedia.org/wiki/C%2B%2B14#Function_return_type_deduction (24.8.2018)
20
Grimm: S. 20.
21
Grimm: S. 22.
22
Grimm: S. 23f.
16• Direktes Initialisieren von Klassenelementen: int x = 5; (wie bei Java)
.[TourDeC++11/classMemeberInitializer.cpp]23
• Steuerung der compilergenerierten Konstruktoren+Operatoren: default, delete
.[TourDeC++11/defaultedDeletedMethods.cpp]24
• Steuerung der Vererbung von Methoden: override, final (wie bei Visual-C++/C#)
Aufgabe:
Ergänzen Sie das Beispiel initializerListConstructor.cpp in der Weise, dass Sie folgende
Konstruktoren zur Verfügung haben:
• Standardkonstruktor
• Initialisiererliste für Integer
• Konstruktor mit zwei Integer
Prüfen Sie nun, welche Instanzierung welchen Konstruktor aufruft:
• Klasse k1;
• Klasse k2{};
• Klasse k3(3,4);
• Klasse k4{3,4};
Entfernen Sie nun nach und nach die Konstruktoren, damit Sie erkennen, welche dieser In-
stanzierungen auch auf andere Implementierungen ausweichen können.
Vereinheitlichte Initialisierung
C++ 11 hat eine einheitliche Initialisierungs-Syntax für
• Strukturen
Klassen, die ausschließlich public members haben und keine Konstruktoren aufweisen.
• C-Arrays
• Container
• Im Konstruktor
• Arrays auf dem Heap
Aufgabe:
Compilieren und Analysieren Sie das Beispiel Kernsprache/uniformInitialisation.cpp25
23
Grimm: S. 24f.
24
Grimm: S. 27f.
25
Grimm: S. 120.
172.3 Lambda-Funktionen
• Funktionen ohne Namen (anonym)
• Lassen sich dort einsetzen, wo Funktionszeiger oder Funktoren (ausführbare Objekte, das
sind Instanzen von Klassen, die den ()-Operator implementieren) vorkommen.
Syntax:
[*1](*2){*3}
*1: Bindung an den lokalen Kontext
[]: Keine Bindung
[=]: Alle Werte werden kopiert ( Schnappschuss ).
[&]: Alle Werte werden referenziert .
*2: Lau fzeitpar ameter
*3: Implementierung
Aufgabe:
• Compilieren und analysieren Sie das Beispiel anhang_a/lambda.cpp
• Ergänzen Sie das Beispiel um einen Anufruf mit einem Funktionszeiger und einem
Funktor.
Bindung an den Kontext
Sollen Variablen aus dem aktuellen Kontext in die Lambdafunktion hineingenommen werden,
müssen diese in den eckigen Klammern angegeben werden. Mit einem vorangestellen & wird die
Variable als Referenz übergeben, sonst wird sie kopiert. Sollen alle Variablen als Referenz gebunden
werden, steht & allein in der eckigen Klammer, ein = allein kopiert alle Variablen.
Aufgabe:
Ergänzen Sie Die Bibliotheksanwengung um eine show_sort_title(), die die Medien nach Titel
sortiert ausgibt. Die Funktion soll wie show_sort_author() aus Kap. 3.3 aufgebaut sein, mit
dem Unterschied, dass statt dem Funktor eine Lambda-Funktion verwendet wird. Der Vektor
wird in der Funktion show_sort_title() definiert und in den Kontext der Lambda-Funktion
gebunden.
Ab C++14 können auch Variablen explizit aus dem Kontext vorbelegt werden, dabei kann auch
die move-Funktion verwendet werden:
auto lambda = [value = std::move(ptr)] {return *value;};
18Aufgabe:
Ändern Sie lambda.cpp so ab, dass der Divisor variabel ist. Der Divisor soll nun
• durch eine Variable aus dem Kontext bestimmt werden,
• durch Zuweisung einer Zahl belegt werden.
Generische Lambdas (C++14)26
In C++11 mussten die Typen der Parameter deklariert werden. Mit C++14 können auch die
Parameter mit auto allgemein gehalten werden.
auto lambda = []( auto x , auto y ) { return x + y ;};
Dieser Code ist äquivalent zu einem ausführbaren Klassentemplate (Funktor):
struct unnamed_lambda
{
template < typename T , typename U >
auto operator ()( T x , U y ) const { return x + y ;}
};
auto lambda = unnamed_lambda {};
Generische Lambdas sind innerhalb generischem Code, also bei der Implementierung von Tem-
plates nützlich. Auch der Umgang mit variant wird dadurch einfacher (s. Kap. 4.3).
bind und function
bind macht aus bestehenden Funktionsobjekten neue, indem es Argumente an den aktuellen
Kontext bindet und Platzhalter erklärt.
function kann einem Funktionsobjekt einen Namen zuweisen, sodass ein aufrufbares Objekt ent-
steht.
Grimms Kommentar zu diesem Thema in Abb. 5.
Aufgabe:
• Compilieren und Analysieren Sie folgendes Beispiel28 : 20-29 (bindAndFunction.cpp)
• Stellen Sie das Beispiel auf auto/Lambdafunktion um.
26
https://en.wikipedia.org/wiki/C%2B%2B14 (27.8.2018)
27
Grimm: S. 456
28
Grimm
19Abbildung 5: bind und function27
2.4 Move vs. Copy
Problem: C++ kopiert viel zu viel:
• Objekte werden in die Container kopiert
Hat das Objekt nach dem Kopiervorgang noch eine Verwendung?
• Anders in Java, C#, Visual C++:
Referenzen werden kopiert + Garbage-Collector
Move ermöglicht performanteres Übertragen von Objekten: Abb. 6.
• Wann wird move verwendet?
explizit: a = std::move(b);
implizit: Rechts steht ein Rvalue
29
Grimm: S. 32
20Abbildung 6: Move vs. Copy29
• Was ist ein Rvalue?
– Ein Ausdruck, der nur rechts vom Gleichheitszeichen stehen darf
– Ausdruck ohne Name: a = MyObject();
Die Syntax für Move-Konstruktor und Move-Zuweisung:
MyClass ( MyClass &&) { ... }
MyClass & operator =( MyClass &&) { ... }
Zu beachten sind dabei folgende Regeln:
• Regel der großen Fünf : Wenn einer der großen Fünf implementiert wird, müssen alle fünf
implementiert werden.
• Nullregel : Es ist besser keinen der großen Fünf zu implementieren30 .
Sollen einzelne der implizit durch den Compiler generierten Operationen verboten werden, kann
dies mit delete erfolgen.
class Simple {
private :
int * daten { nullptr }; // Roher Zeiger !!!
Simple ( int n ) : daten { new int [ n ] } {}
~ Simple () { delete [] daten ; }
Simple ( const Simple &) = delete ; // keine Kopie
Simple & operator =( const Simple &)= delete ; // keine Zuweisung
Simple ( Simple &&) = delete ; // kein Verschieben
Simple & operator =( Simple &&)= delete ; // keine V ers ch ie bzu we is ung
};
30
Wolf: 291.
21Aufgabe:
Compilieren und Analysieren Sie Move.cpp. 5x wird ein großes Array kopiert. Das soll im
Folgenden verhindert werden:
• Ergänzen Sie in der Klassendefinition den Move-Konstruktor und die Move-Zuweisung.
Jetzt sollten es nur noch zwei/drei (je nach Compiler) Kopiervorgänge sein.
• An einer Stelle können Sie durch Einfügen eines std::move ein weiteres Kopieren ver-
hindern.
Die Möglichkeit Freispeicher durch eine andere Instanz übernehmen zu lassen, verhindert zwar
oftmals unnötiges Kopieren. Schöner wäre es aber, Instanzen direkt im Speicher des Contai-
ners instanzieren zu können. Die neuen emplace-Funktionen der Standard-Container ermöglichen
dies. Bei einem emplace werden die Parameter so angegeben, wie sie für einen Konstruktoraufruf
benötigt werden: Beispiel für vector31
Aufgabe:
Ändern Sie das Move-Beispiel so ab, dass durch emplace_back ein move in den vector ver-
mieden wird.
2.5 Verschiedenes (Teil 2)
Definition von Typen
Ein weiteres neues Sprachelement ist using als Ersatz von typedef:
typedef struct my_struct my_type ;
// ist gleichbedeutend mit
using my_type = struct my_struct ;
Mit using lassen sich Templates (teilweise) binden32 :
template < typename T , int Line , int Column >
class Matrix ;
template < typename T , int Line >
using Square = Matrix ;
template < typename T , int Line >
using Vector = Matrix ;
31
http://www.cplusplus.com/reference/vector/vector/emplace/
32
Grimm: S. 186
22Typsichere Aufzählungen
enum class:
• Typsicher, da nicht implizit zu int konvertierbar
• Keine namespace pollution
• ABER: Keine Rückkonvertierung zum Namen möglich
[Kernsprache/enum.cpp]33
Neue Literale
• Raw Strings34
R"Trenner(raw string)Trenner"
Trenner: beliebige (auch leere) Zeichenkette
• Benutzerdefinierte Literale
Literale der Form: wert_einheit (25.6_km, 2345_s, 2.2e24_kg)
Beispiel: [userDefinedLiterale.cpp]35
• Binäre Literale: Mit C++14 lassen sich Literale der Form 0b001010011 schreiben.
• Digit Seperator: Zur Lesbarkeit ist es möglich das einfache Hochkomma einzufügen:
0b0’0101’0011, 123’345’678
constexpr
Mit C++11 wurde constexpr als Verbesserung von #define und const eingeführt: Typssicher wie
const, Auswertung zur Compilezeit wie #define:
constexpr double e a r t h _ g r a v i t a t i o n a l _ a c c e l e r a t i o n = 9.8;
constexpr int get_five () { return 5;}
int some_value [ get_five () + 7]; // Create an array of 12 integers . Valid C ++11
Mit C++17 kommt constexpr-if hinzu:
if constexpr ( a n y _ c o m p i l e _ t i m e _ b o o l e a n _ e x p r e s s i o n )
{
// this is only compiled , if a n y _ c o m p i l e _ t i m e _ b o o l e a n _ e x p r e s s i o n is true
int i = 5;
}
else
{
double i = 5.;
}
33
Grimm: S. 202f.
34
Grimm: S. 205f.
35
Grimm: S. 213f.
23Dies findet Anwendung mit den neuen Möglichkeiten Typinformationen zur Compilezeit abzufra-
gen.
Variadic Templates
Templates mit variabler Zahl von Parametern
• Element zur funktionalen Programmierung
template < typename ... Args >
function ( Args ... args )
• args ist dabei ein „Parameter Pack”
• args... ist die entpackte Parameterliste
# include < iostream >
# include < vector >
# include < algorithm >
# include < typeinfo >
template < typename T = int >
void mischmasch ( T val =0 L ) {
std :: cout . width (3);
std :: coutDie Funktion soll dann folgendermaßen aufgerufen werden:
string signatur , autor , titel ;
char typ ;
int seitenzahl , spieldauer ;
stringstream ss (" A01 , Heinz Autor , Der Titel ,B ,125 ,0");
bool ok = split ( ss , ’ , ’ , signatur , autor , titel , typ , seitenzahl , spieldauer );
3 Templates36
Templates bieten eine Form der generischen Implementierung. Man unterscheidet Funktionstem-
plates von Klassentemplates.
3.1 Funktionstemplates
Motivation: Identischer Code, der sich nur in einigen Typen unterscheidet
Beispiel: [listings/015/Templates001/main.cpp].
Implementierung: [listings/015/Templates002/main.cpp]
Umgang mit Ausnahmen, also Typen, die eine spezielle Implementierung erfordern:
std :: string str1 {" Hallo "} , str2 {" Welt "};
std :: cout3.2 Klassentemplates
Definition: [listings/015/SimpleCTemp/CTemp.h]
Instanzieren:
CTemp < double > dval ;
CTemp < std :: string > sval ;
Für Templates wird erst dann Code gebildet und compiliert, wenn es für einen bestimmten Typ
instanziert wird. Für Typen, die oft vorkommen, kann ein Template auch „auf Vorrat” instanziert
werden:
template < class T >
class Y // template definition
{
void mf () { }
};
template class Y < char * >; // explicit instantiation
Damit dieses Template dann aber nicht erneut an anderen Stellen compilert wird wird, muss es
dort mit
extern template class Y < char * >;
bekannt gegeben werden. Dieses Vorgehen rentiert sich aber nur bei entsprechend großen Tem-
plates. Beim Linken wir überflüssiger Code (z.B. mehrfache Instanzierungen eines Templates für
denselben Typ) entfernt.37
3.3 Templates der Standardbibliothek
Container der Standardbibliothek38 →vector, map, array (C++11)
Eine typische Verwendung von std::map finden Sie in Abb. 7.
Um über Container zu iterieren gibt es das Iteratorkonzept. Der Iterator ist ein spezieller Zeiger auf
ein Element des Containers. Mit der Methode begin() erhält man den Iterator, der auf das erste
Element des Containers zeigt, end() ist nach dem letzen Element positioniert. Der ++-Operator
schiebt den Iterator auf das nächste Element. Eine Schleife über alle Elemente lässt sich daher
so formulieren:
std :: map < std :: string , std :: string > phonebook ;
// fill phonebook with data
std :: map < std :: string , std :: string >:: iterator mapIt ;
for ( mapIt = phonebook . begin (); mapIt != phonebook . end (); mapIt ++)
std :: cout first# include
// definition
std :: map < std :: string , int > mymap ;
// Element hinzuf " ugen / " uberschreiben
mymap [" eins "] = 1;
// Element abfragen . Wenn nicht existent , wird es eingef " ugt .
int i = mymap [" eins "];
// Element abfragen . Wenn nicht existent , wird eine Exception geworfen .
int j = mymap . at (" drei ");
// Abzahl der Elemente in der map
int a = mymap . size ();
// Anzahl der Elemente mit einem bestimmten Schl " ussel ( kann 0/1 sein )
int k = mymap . count (" zwei ");
Abbildung 7: Verwendung von std::map
Aufgabe:
Ergänzen Sie die Bibliotheksanwendung. Um die Datei nicht jedes mal auf’s Neue lesen zu
müssen, sollen die Daten in einer std::map39 abgelegt werden. Dazu bekommt die Medienver-
waltung jetzt interne Daten, wird vom Funktionsmodul zur Klasse.
• Legen Sie die Klasse MedienverwaltungClass mit den internen Daten
std::map medium_map; an.
• Schreiben Sie eine Methode load(), die die Datei einliest und das Verzeichnis füllt.
Als Schlüssel soll dabei die Signatur dienen, als Wert eine Instanz von Medium. Diese
Funktion soll im Konstruktor aufgerufen werden.
Nehmen Sie die Implementierung von bool isSignatureInFile(const string& sig) als
Vorlage.
• Schreiben Sie eine Methode bool checkDuplicate(const string& signatur), die prüft,
ob der gegebene Schlüssel in der map vorhanden ist.
• Schreiben Sie eine Methode void show(), die über das Verzeichnis iteriert und al-
le gespeicherten Medien ausgibt. Beachten Sie, dass das Element einer std::map ein
Schlüssel-Wert-Paar ist. Auf den Schlüssel wird mit .first, auf den Wert mit .second
zugegriffen.
• Schreiben Sie eine Methode int addMedium(const Medium& medium), die checkDuplicate
aufruft und das Medium an die Datei anhängt, danach das Verzeichnis mit load aktua-
lisiert.
• Verwenden Sie den neuen Code im Hauptprogramm.
Da die Medium-Instanzen direkt in die Map kopiert werden, funktioniert dieses Konstrukt nicht
mit Instanzen einer Klassenhierarchie. In diesem Fall müssen die Instanzen im Freispeicher
liegen und in der Map die Zeiger verwaltet werden.
27• Legen Sie in MedienverwaltungClass eine map an und
füllen Sie diese gleichfalls in der load-Methode. Zur Erzeugung der Instanz kann die
Fabrikfunktion createMedium verwendet werden.
• Ergänzen Sie die Methode show(), so dass auch diese Map ausgegeben wird.
Algorithmen der Standardbibliothek
Algorithmen der Standardbibliothek40 → for_each(), find(), find_if(), copy(), count(),
count_if(), sort()
Viele dieser Algorithmen benötigen eine Funktion als Parameter. Die kann eine „normale” Funk-
tion, aber auch ein Funktionsobjekt sein. Ein Funktionsobjekt (Funktor) ist eine Klasse, die den
()-Operator implementiert:
class MyFunctor {
private :
int internal ;
public :
MyFunctor ( int i ) : internal ( i ) {}
bool operator ()( int i ) { return i < internal ; }
}
Aufgabe:
Ergänzen Sie die Bibliotheksanwendung um eine Funktion show_sort_author(), die die Medien
nach autor sortiert ausgibt:
• show_sort_author() kopiert alle Instanzen aus der Medium-Map in einen Vektor. Dazu
wird ein Funktor angelegt, der einen Vektor enthält. Zum Kopieren wird die for_each()-
Funktion verwendet, die für jeden Map-Eintrag den Funktor aufruft, der wiederum die
Medium-Instanz (.second) dem internen Vektor anhängt. Die for_each()-Funktion gibt
den veränderten Funktor nach der Ausführung als Rückgabewert zurück. Nun kann der
Vektor aus dem Funktor ausgelesen werden.
• Schreiben Sie eine Funktion
bool comp_media(const Medium& m1, const Medium& m2)
die true zurück gibt, falls author von m2 größer ist.
• Sortieren Sie den Vector mit der sort-Funktion.
• Geben Sie den Vektor mit Hilfe der copy-Funktion aus. Das Kopierziel ist ein output
stream iterator41
39
Nützliche Informationen zur Standardlibrary findet man unter http://cplusplus.com
40
http://www.cplusplus.com/reference/algorithm/
41
http://www.cplusplus.com/reference/iterator/ostream_iterator/algorithm/ (3.5.2019)
284 C++11 Standardbibliothek
4.1 Neue Container
Tupel
tuple ist die Verallgemeinerung von pair. Es nimmt mehrere Daten verschienenen Typs auf.
Für tuple existieren diverse Hilfsfunktionen in der Standardbibliothek:
.[DieStandardbibliothek/helperTuple.cpp]42
• Z. 10: make_tuple erzeugt ein Tupel aus einzelnen Werten.
• Z. 23: Zusammen mit dem Referenzwrapper ref/cref wird ein Tupel aus Referenzen er-
zeugt.
• Z. 50: tie erzeugt ein Tupel aus Referenzen für alle Werte. Dies ist eine einfache Möglichkeit
die Werte eines Tupels Variablen zuzuweisen.
Aufgabe43 :
Ergänzen Sie folgende Funktion, so dass sie vier heterogene Typen zurück gibt und geben Sie
die Werte im Hauptprogramm aus:
??? returnFourValues () {
int a = 5;
double b = 10.2;
std :: string c = " test ";
bool d = true ;
return ??? ;
}
Der klassische C++-Weg bestand darin, eine Struktur zu definieren, die die vier Typen bindet,
und diese Struktur als Rückgabewert zu verwenden. In C++11 gibt es einen einfacheren und
besseren Weg.
Mit C++17 soll die Möglichkeit kommen, das Rückgabetupel direkt einzelnen Variablen zuzuwei-
sen, die im selben Schritt auch deklariert werden:
auto [a, b] = getTwoReturnValues();44
Anmerkung: Der Referenz-Wrapper ist auch bei der Lösung des klassischen Problems hilfreich,
dass Container keine Referenzen enthalten können. Der Container kann den Referenz-Wrapper
als Element haben, dieser muss aber mit emplace direkt im Container erzeugt und belegt werden.
[TourDeC++11/referenceWrapper.cpp]
42
Grimm: S. 417.
43
Grimm: Aufg. 20-7, S. 422. Lösung: fourReturnValues.cpp
44
https://en.wikipedia.org/wiki/C%2B%2B17 (27.8.2018)
29Aufgabe:
Ändern Sie MedienverwaltungClass so ab, dass sie die Daten in drei Containern speichert:
• map mit allen Büchern
• map mit allen CDs
• map mit Referenzen auf alle Bücher und CDs
Ergänzen Sie die Klasse um
• showBucher(), die die Bücher ausgibt,
• showCD(), die die CDs ausgibt,
• showMedia(), die alle Medien ausgibt.
Array
std::array lässt sich kurz und knapp charakterisieren: std::array vereint die Speicher-
und Laufzeitanforderungen des C-Arrays mit einem STL-konformen Interface. Das
Beste aus beiden Welten.45
Für array existieren diverse Hilfsfunktionen in der Standardbibliothek:
.[DieStandardbibliothek/arrayInterface.cpp]46
• Z. 17, 42: Ausgabe über std::copy
• Z. 33: Ausgabe über Range-For
• Z. 31: {{...}}: geht auch mit einfachen Klammern, war bei ersten Compilern so nötig, um
den Aufruf des Konstruktors zu umgehen.
• Z. 47: std::accumulate, was in Z. 48 zur Bildung des Mittelwertes genutzt wird.
• Z. 58: std::swap vertauscht Arrays.
• Z. 70: Zugriff wie auf tuple möglich
Die Headerdateien der angegeben Funktionen finden sich in und .
Aufgabe:
Compilieren und Analysieren Sie folgende Beispiele:
• arrayInterface.cpp
• DieStandardbibliothek/Aufgaben/arrayViolation.cpp47 :
Greifen Sie über die Indexgrenzen des Arrays hinaus. Greifen Sie einmal mit dem Inde-
xoperator [] bzw. mit der at()-Elementfunktion zu.
45
Grimm: S. 424.
46
Grimm: S. 424.
30Einfach verkettete Liste
std::forward_list ist ein sequenzieller Container mit einem eingeschränkten Inter-
face. Als einfach verkettete Liste benötigt sie nicht mehr Speicher als die entspre-
chende C-Datenstruktur. std::forward_list ist für den speziellen Einsatz konzipiert:
Wenn die optimierte Speicheranforderung, das schnelle Einfügen oder Entfernen von
Elementen gefragt ist und der wahlfreie Zugriff nicht benötigt wird, ...48
Für forward_list existieren diverse Memberfunktionen. Im Beispiel forwardListManipulate.cpp49
wird die Verwendung vorgestellt:
• Z. 12: .push_front() stellt ein neues Element an den Anfang der Liste.
• Z. 27: .erase_after() löscht das Element nach der Iteratorposition.
• Z. 31: .insert_after() fügt ein Element nach der Iteratorposition ein.
• Z. 40: .splice_after() fügt eine Liste in eine andere ein.
• Z. 46: .sort() sortiert die Liste.
• Z. 52: .reverse kehrt die Reihenfolge um.
• Z. 58: .unique() entfernt direkt aufeinanderfolgende Duplikate.
Aufgabe50 :
• Bestimmen Sie die Anzahl der Elemente einer std::forward_list.
• Betrachten Sie die Musterlösung forwardListSize.cpp: Die dort implementierte Tem-
platefunktion sizeOf funktioniert prinzipiell auch für vector.
– Verallgemeinern Sie die Templatefunktion so, dass es auch für einen vector funk-
tioniert.
– Ergänzen Sie eine Spezialisierung des Templates für vector, die die Größe des
Vectors aus der Differenz der Iteratoren (Anfang/Ende) berechnet.
Hashtabellen
Mit C++98 standen mit set, multiset, map und multimap Container zur Verfügung, die sich wie
Hashtabellen anfühlen, aber sortierte Schlüssel haben. Die Zugriffszeiten sind daher schlechter als
bei Hashtabellen. Mit C++11 gibt es nun die vier Container mit dem unordered_-Prefix. Diese
Implementierungen sind Hashtabellen.
47
Grimm: S. 428.
48
Grimm: S. 428
49
Grimm: Beispiel 20-22, S. 429.
50
Grimm: Aufg. 20-11 S. 431.
31Aufgabe51 :
Vergleichen Sie die Zugriffszeit von map und unordered_map.
Legen Sie zweigroße assoziative Container vom Datentyp map und
unordered_map an und belegen sie diese mit Werten.
Der zufällige Zugriff auf die Elemente erfolt nach Einführung der Zufallszahlen, die Zeitmes-
sung wird bis zur Einführung der Zeitbibliothek zurückgestellt.
4.2 Algorithmen
Neue Algorithmen52 → all_of, any_of, copy_if, is_sorted, minmax_element
Mit C++17 soll begonnen werden die Parallelisieung der Algorithmen zu standardisieren.
Aufgabe:
Stellen Sie die Implenetierung von lambda.cpp auf die Verwendung von copy_if um.
Aufgabe:
Ergänzen Sie MedienverwaltungClass um eine Methode checkDuplicateAlgo, die unter Ver-
wendung von std::any_of die Prüfung durchführt.
template < class InputIt , class UnaryPred >
bool any_of ( InputIt first , InputIt last , UnaryPred pred );
4.3 Neue Funktionalität
Smart Pointer
• auto_ptr: C++03 Implementierung: Keine Unterscheidung von move und copy → passt
nicht ins C++11-Konzept
• unique_ptr löst auto_ptr ab.
• shared_ptr: arbeitet mit Referenzzähler
• weak_ptr: Wrapper um shared_ptr: „Ein nicht aktivierter shared_ptr”. Die „Aktivierung”
erfolgt durch .lock(). Kann für den Umgang mit zyklischen Referenzen nützlich sein (s.
Abbildung 8)
32Abbildung 8: Zyklische Referenz53
Aufgabe:
• Compilieren und Analysieren Sie das Beispiel (TourDeC++11/uniquePtrMove.cpp).
Greifen Sie auf die Pointer unique1 und unique2 vor und nach dem Move zu.
• Compilieren und analysieren Sie Beispiel anhang_a/smart_ptr01.cpp (Beispiel mit
Deleter)
• Compilieren und Analysieren Sie das Beispiel (TourDeC++11/sharedWeakPtr.cpp).
• Compilieren und Analysieren Sie das Beispiel Zyklische Referenz 54 .
Reguläre Ausdrücke
Prinzipielles Vorgehen
• Erkläre den regulären Ausdruck
std::regex r();
• Prüfe, ob die Zeichenkette exakt dem Muster entspricht:
std::regex_match (,r);
51
Grimm: Aufg. 20-13 S. 447: mapHashComparison.cpp.
52
http://www.cplusplus.com/reference/algorithm/
53
Linux Magazin 04/2013, S. 106.
54
Listing 4 aus LM 03/2013
33• Halte das Ergebnis der Suche
std::smatch s; std::regex_search(, s, r);
s enthält nun die Treffer.
• Suche und ersetze Treffer:
std::regex_replace (,r,);
Über $1, $2, ... kann dabei auf Gruppen des regulären Ausdrucks zugegriffen werden.
Aufgabe:
• Kompilieren und analysieren Sie Beispiel anhang_a/regexpr.cpp55
• Bauen Sie einen Parser für kommaseparierte Zeichenketten, die den Typ (Integer, Byte,
Wert56 , String) erkennt.57
Anmerkung: Es gibt einen REGEX-Tester58
Zufallszahlen
Die Erzeugung von Zufallszahlen erfolgt nach folgendem Muster:
• Erzeugung eines Zufallszahlenstroms
• Abbildung auf die gewünschte Verteilungsfunktion
• Abrufen der Zufallszahlen
Im Beispiel [DieStandardbibliothek/distribution.cpp] wird die Verwendung vorgestellt:
• Z. 25/28: Definition des Zufallszahlenstroms
• Z. 33-39: Abbildung auf verschiedene Verteilungsfunktionen
• Z. 47-50: Abrufen der Zufallszahlen
Aufgabe59 :
Ergänzen Sie die map/unordered_map-Übung:
• Greifen Sie in einer Schleife auf mapSize/10 zufällige Schlüssel zu und geben Sie diese
aus.
55
Wolf: S. 436f.
56
C++-Literal der Form 22_km
57
Musterlösung: Regex.cpp
58
http://regex101.com
59
Teil von DieStandardbibliothek/Aufgaben/mapHashComparison.cpp
34Zeitbibliothek
Die C++11-Zeitbibliothek kennt folgende wichtigen Klassen:
• Zeitpunkt std::chrono::time_point
• Dauer std::chrono::duration
• Zeitgeber std::chrono::system_clock
Mit folgendem Code lässt sich daher eine Zeitmessung bauen:
auto start = std :: chrono :: system_clock :: now ();
// code for measurement
std :: chrono :: duration < double > dur = std :: chrono :: system_clock :: now () - start ;
std :: coutTypvarianten61
Alternative Typen konnten bisher über union abgebildet werden. Dabei blieb es aber dem Ent-
wickler überlassen, wie er sicher stellen will, welcher Typ gerade aktuell ist. std::variant gibt
hier Unterstützung:
using ItsOk = std :: variant < std :: vector < int > , double >;
int main () {
// set the variant to vector , this constructs the internal vector
ItsOk io = std :: vector {22 , 44 , 66};
// reset to double - the internal vector is properly destroyed
io = 13.7;
// There ’ s no vector in the variant - throws an exception
int i = std :: get < std :: vector < int > >( io )[2];
}
Die Verwendung von gemeinsamen Eigenschaften der Typen erfolgt über std::visit(, ):
std :: ostream & operator < < ( std :: ostream & os , MyVariant const & v ) {
// all variants have to implement the stream operator
std :: visit ([& os ]( auto const & e ){ osBeispiel62
Typinformationen
Da mit den Templates, auto und decltype die starre Typisierung immer mehr aufgebrochen wird,
existieren mit C++11 erweiterte Möglichkeiten Eigenschaften von Typen zur Compilezeit zu
überprüfen.
Alle C++-Typen gehören exakt einer der folgenden Typenkategorien an:63
is_void
is_integral
is_fl oating_p oint
is_array
is_pointer
is_reference
is_member_object_pointer
is_member_function_pointer
is_enum
is_union
is_class
is_function
Davon abgeleitet gibt es zusammengesetzte Kategorien. So bedeutet is_arithmethic:
is_integral ODER is_floating_point.64 [DieStandardbibliothek/typeCategories.cpp]
Desweiteren könne Typeigenschaften abgefragt werden: is_abstract, is_polymorphic, is_signed, ...
Überblick65
Aufgabe:
Ändern Sie die Implementierung des Templates bigNum66 so ab, dass
• im Falle von numerischen Argumenten der Kleinervergleich angewendet wird.
• in allen anderen Fällen das Ergebnis von .size() verglichen wird.
Für die Lösung gibt es zwei Ansätze:
• is_arithmethic::type liefert true_type oder false_type. Damit lässt sich ein weites
Funktionstemplate aufrufen, das als 3. Parameter true_type oder false_type nimmt.
• is_arithmethic::value liefert true oder false. Damit lässt sich mit if constexpr
innerhalb des Templates verzweigen.
62
https://en.cppreference.com/w/cpp/utility/any
63
Grimm: S. 343.
64
Grimm: S. 345.
65
http://www.cplusplus.com/reference/type_traits/|
66
Kapitel 3.1
375 Multithreading
Der Lebenszyklus eines Threads lässt sich wie folgt kontrollieren:
• Erzeugen eines Threads:
std::thread t (, , ...);
Die Parameter werden grundsätzlich in den Thread kopiert. Sollen sie als Referenz überge-
ben werden, muss dies durch std::ref gekennzeichnet werden.
• Synchronisieren von Threads
t.join();
• Entkoppeln von Threads
t.detach();: Dies funktioniert zwischen Kind und Enkel-Thread. Der Hauptthread muss
immer auf alle abgeleiteten Threads warten.
Die Sicherung von Ressourcen erfolgt über Mutex (mutual exclusion, wechselseitiger Ausschluss):
• std::mutex m; definiert Mutex
• std::lock_guard g(m); setzt Mutex für die Lebenszeit von g oder bis g.unlock()
Ist hingegen eine Operation atomar, so ist eine Sicherung nicht nötig. Eine atomare Operation ist
eine Operation auf einen atomaren Datentypen. Als atomare Datentypen gibt es einerseits die built
in Typen atomic_bool, atomic_char, ..., andererseits können auch benutzerdefinierte Typen als
atomar gekennzeichnet werden, indem sie in das Template atomic genommen werden. Die
Verwendung atomarer Typen zeigt [Multithreading/atomic.cpp].67
Threads lassen sich über Semaphore synchronisieren. Der „Sender” signalisiert dem „Arbeiter”,
wann er weitermachen darf. Bestandteile:
• Einfache (boolsche) Variable: Das Semaphor
• std::lock_guard zum Setzen des Semaphors
• std::unique_guard zum Lesen des Semaphors
• std::condition_variable zum Triggern des Arbeiters durch den Sender
Threads lassen sich auch über future/promise, d.h. über einen Rückgabewert synchronisieren:
// promise definieren
promize prom;
// future ableiten
fut = prom.get_future();
// thread starten
thread t(f, prom, ...) f(prom, ...)
// thread-Bearbeitung
// Ergebnis-R"uckgabe
// Ergebnis erwarten prom.set_value(e)
e = fut.get()
67
Grimm: S. 230.
38Beispiel: Multithreading/futurePromise.cpp
Sollen mehrere Threads auf dasselbe Ergebnis warten, kann dies mit einem shared future erfolgen.
Beispiel: sharedFuturePromise.cpp
Der Future/Promise-Mechanismus lässt sich über die async-Funktion vereinfachen:
• auto f = std::async (launch::async,, ); liefert ein Future-Objekt
• Synchronisation über f.get()
Je nach Programmierstil enthalten Programme globale Variablen. Diese verhindern, dass ein Pro-
gramm in mehreren Threads laufen kann. Eine Möglichkeit ein solches Programm threading -fähig
zu machen, ist, Variablen, die nun pro Thread benötigt werden, als „threadlokal” zu definie-
ren. Threadlokale Daten bilden ein Array, in welchem jeder Thread sein eigenes Element hat:
[Multithreading/threadLocal.cpp]68
Aufgabe:
Compilieren und analysieren Sie folgende Beispiele:
• anhang_a/thread01.cpp (Threads erzeugen)
• anhang_a/thread02.cpp (Mutex)
• Bsp. 4-8 (conditionVariable.cpp)69
Erweitern Sie dieses Beispiel auf mehrere worker.
• Bsp. 18.5 (futurePromise.cpp)70
Stellen Sie dieses Beispiel auf async um.
• Beispiel aus Linux Magazin 04/2012 (async): dotProduct.cpp/dotProductPara.cpp
Beim gcc ist dazu die Linker-Option -pthread nötig!
6 Quellen
Grimm, Rainer C++11 für Programmierer, 2014
Wolf, Jürgen Grundkurs C++, 3. Auflage
68
Grimm: S. 269
69
Grimm: S. 58.
70
Grimm: S. 292.
39Vielen Dank für Ihre Aufmerksamkeit
Ihr Referent und das Team von
IT-Schulungen.com
Fon +49 (0) 911 650 08 - 30
Fax +49 (0) 911 650 08 - 399
Mail info@it-schulungen.com
Web www.it-schulungen.com
Education Center der New Elements GmbH
Thurn-und-Taxis-Straße 10
90411 Nürnberg
www.newelements.de
www.IT-Schulungen.com New Elements GmbH | IT-
Schulungen.com
40Sie können auch lesen