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.de
Inhaltsverzeichnis 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 3
Abbildung 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 7
Diese 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. 8
string 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&&) 10
In 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 11
Dies nennt man Überschreiben von Methoden. Auf die überschriebene Methode der Mutterklasse kann direkt aus der Kindklasse zugegriffen werden: void print () const { std :: cout
Aufgabe: 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. 13
Abstrakte 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/ 14
2.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 :: cout
nullptr 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. 17
2.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;}; 18
Aufgabe: Ä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 19
Abbildung 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 20
Abbildung 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. 21
Aufgabe: 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 22
Typsichere 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. 23
Dies 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 :: cout
Die 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 :: cout
3.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) 28
4 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) 29
Aufgabe: Ä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. 30
Einfach 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. 31
Aufgabe51 : 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) 32
Abbildung 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 34
Zeitbibliothek 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 :: cout
Typvarianten61 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 ){ os
Beispiel62 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 37
5 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. 38
Beispiel: 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. 39
Vielen 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 40
Sie können auch lesen