Kapitel 19. Neue Bibliotheken

In diesem Kapitel:

Mit der Bibliothek für reguläre Ausdrücke erhält C++11 ein lang vermisstes Feature, sind doch reguläre Ausdrücke das Standardwerkzeug für anspruchsvolle String-Manipulationen. Aber auch die Type-Traits-Bibliothek mit ihrer Introspektionsfähigkeit zur Übersetzungszeit, die neuen Bibliotheken zu Zufallszahlen und zur Zeit oder die praktischen Referenz-Wrapper werden bald Standardwerkzeuge des professionellen C++-Programmierers sein.

Reguläre Ausdrücke

<regex>

Bevor wir uns die Syntax der regulären Ausdrücke in C++11 genauer anschauen, zunächst ein kleines Beispiel. In Listing 19.1 wird aus einem String die E-Mail-Adresse extrahiert und analysiert.

regexEmail.cpp

Listing 19.1 verfolgt die klassischen drei Schritte beim Umgang mit regulären Ausdrücken in C++11:

  1. Erklären Sie den regulären Ausdruck (Zeile 16, regExprStr).

  2. Halten Sie das Ergebnis der Suche (Zeile 19, rgx).

  3. Verarbeiten Sie das Suchergebnis weiter (Zeilen 22 bis 37).

Abbildung 19.1 zeigt die Ausgabe von Listing 19.1.

Aus dem Text emailDescription in Zeile 10 wird mithilfe des regulären Ausdrucks regExprStr die E-Mail-Adresse herausgefiltert und für die Auswertung zur Verfügung gestellt. In den Zeilen 22 bis 37 folgt die Auswertung. smatch[0] (Zeile 28) stellt die ganze E-Mail-Adresse, smatch[1] die erste Erfassungsgruppe (capture group) und smatch[2] (Zeile 30) die zweite Erfassungsgruppe zur Verfügung. Die erste Erfassungsgruppe ist der lokale Anteil, die zweite Erfassungsgruppe ist der Domänenanteil der E-Mail-Adresse. Mit smatch.prefix() bzw. smatch.suffix() (Zeilen 34 und 35) kann der Text vor bzw. nach der E-Mail referenziert und ausgegeben werden.

Raw-String-Literale

Für wen reguläre Ausdrücke nicht zum täglichen Brot gehören (den Autor eingeschlossen), für den ist der Ausdruck R"(([\w.%+-]+)@([\w.-]+\.[a-zA-Z]{2,4}))" in Zeile 13 nur schwer verdaulich. Bevor wir uns die Syntax genauer anschauen, noch eine kleine Bemerkung. Durch R"(...)" wird ein Raw-String-Literal in C++11 erklärt. Damit ist es nicht mehr nötig, die Backslash-Zeichen »\« im regulären Ausdruck durch einen vorangestellten zweiten Backslash »\« zu maskieren. Das äquivalente String-Literal "([\\w.%+-]+)@([\\w.-]+\\.[a-zA-Z] {2,4})" ist da schon schwieriger zu lesen.

Reguläre Ausdrücke lassen sich in C++11 in sechs verschiedenen Grammatiken spezifizieren.

Per Default wird die ECMAScript-Grammatik in C++11 verwendet, die die mächtigste der sechs vorgestellten Grammatiken darstellt. Daher wird sich meine weitere Ausführung über reguläre Ausdrücke auf die ECMAScript-Syntax beschränken. Die folgende Einführung in die Syntax der regulären Ausdrücke erhebt nicht ansatzweise den Anspruch, vollständig zu sein. Sie soll nur einen pragmatischen Einstieg in reguläre Ausdrücke in der ECMAScript-Grammatik anbieten.

Zeichenklassen sind besondere Zeichenauswahlen. In ECMAScript sind die folgenden Zeichenklassen definiert:

Vordefinierte Zeichenklassen

Für häufig benötigte Zeichenklassen gibt es in ECMAScript vordefinierte Kurzschreibweisen, die durch einen Backslash eingeleitet werden.

Werden die bisher dargestellten Bausteine aneinandergereiht, können die Muster deutlich komplexer werden. Die ECMAScript-Grammatik erlaubt es, Teilmuster durch die runden Klammern »( )« in dem Gesamtmuster zu definieren. Die Grundfrage bleibt aber bestehen. Ist ein vorgegebener String in der Menge der Wörter enthalten, die ein Muster beschreibt?

Erfassungsgruppen

Diese Teilmuster werden Erfassungsgruppen (capture groups) genannt und bieten mächtige Features an. Zuallererst können die Erfassungsgruppen im Suchergebnis abgefragt werden. Ist smatch (Zeile 19) wie in Listing 19.1 das Ergebnis der Suchabfrage, kann mit smatch[0] das gesamte Ergebnis oder auch die nullte Erfassungsgruppe, mit smatch[1] die erste Erfassungsgruppe, mit smatch[2] die zweite Erfassungsgruppe und mit steigendem Index jede weitere Erfassungsgruppe angesprochen werden, die natürlich leer sein kann. Die Nummerierung der Erfassungsgruppen findet in dem Muster von außen nach innen und von links nach rechts statt.

In Listing 19.2 sind Erfassungsgruppen im Einsatz zu sehen.

captureGroup.cpp

Da die regulären Ausdrücke in Listing 19.2 keine zu schützenden Sonderzeichen enthalten, sind Raw-String-Literale nicht notwendig. Kompiliert und ausgeführt, lässt sich schön die Anwendung der Erfassungsgruppen studieren.

Rückwärtsreferenzen

Erfassungsgruppen können nicht nur in dem Suchergebnis, sondern direkt im Muster referenziert werden. Dieses mächtige Feature nennt sich Rückwärtsreferenz. So lässt sich mit \1 die erste, mit \2 die zweite und mit \i die i-te Erfassungsgruppe referenzieren. Dabei ist das Muster nur gültig, wenn für die i-te Rückwärtsreferenz eine i-te Erfassungsgruppe definiert wurde.

In Tabelle 19.7 sind ein paar Beispiele für Rückwärtsreferenzen dargestellt.

Mit der Syntax für reguläre Ausdrücke gewappnet, lässt sich nun auch der reguläre Ausdruck ([\w.%+-]+)@([\w.-]+\.[a-zA-Z] {2,4})für die E-Mail-Adresse aus Listing 19.1 in Prosa übersetzen, der eine E-Mail-Adresse in ihren lokalen und den Domänenanteil zerlegt. Für die erste Analyse des E-Mail-Ausdrucks werden die Erfassungsgruppen ignoriert. Damit lässt sich der reguläre Ausdruck auf [\w.%+-]+@[\w.-]+\.[a-zA-Z]{2,4} vereinfachen.

Der lokale Anteil [\w.%+-]+ besteht aus:

  1. mindestens einem Zeichen ([...]+) aus der Menge der Wortzeichen (\w), einem Punkt (.), einem Plus- (+) oder einem Minuszeichen (-).

Der Domänenanteil [\w.-]+\.[a-zA-Z]{2,4} folgt auf das at-Zeichen @ und besteht aus:

  1. mindestens einem Zeichen ([...]+) aus der Menge der Wortzeichen (\w), einem Punkt (.) oder einem Minuszeichen (-),

  2. einem Punkt (\.),

  3. zwei bis vier ({2,4}) Buchstaben [a-zA-Z].

Die Zeichenkette [...]+ enthält mindestens ein Zeichen aus der Zeichenmenge [...] im Gegensatz zur Zeichenkette [...]*, die auch leer sein kann. Werden zu dem einfachen regulären Ausdruck wieder die Erfassungsgruppen hinzugefügt, lassen sich der lokale und der Domänenanteil aus diesem extrahieren.

Diese Einführung in die ECMAScript-Grammatik sollte ausreichen, um die weitere Funktionalität zu regulären Ausdrücken in C++11 im Detail zu betrachten.

Aufgabe 19-1

Mehr Informationen zu regulären Ausdrücken.

Um tiefer in die Syntax der regulären Ausdrücke einzutauchen, sei auf (Friedl, 2002) verwiesen. Auch Onlinetools wie der REGEXP-EVALUATOR von Jens Henneberg (Henneberg, 2010) leisten wertvolle Dienste, wenn es darum geht, schnell einen regulären Ausdruck auszuwerten.

Diese Muster, die reguläre Ausdrücke beschreiben, werden in C++11-Objekten gekapselt.

basic_regex

Objekte vom Typ regulärer Ausdruck sind Instanzen des Klassen-Templates std::basic_regex, die über ihren Charaktertyp und die Traits-Klasse parametrisiert werden. Dabei legt die Traits-Klasse fest, wie das Objekt Eigenschaften der regulären Grammatik interpretiert. Das hört sich komplizierter an, als es ist, denn in Anlehnung an std::string und std::wstring gibt es zwei Typsynonyme für std::basic_regex, die diese Komplexität verbergen:

Anpassung von regex und wregex

Hier hört aber die Parametrisierung nicht auf. Für Instanzen vom Typ std::regex bzw. std::wregex lässt sich die verwendete Grammatik, Optimierungseigenschaften und Modifikationen der Syntax des resultierenden Objekts an die eigenen Bedürfnisse anpassen. Als mögliche Grammatiken stehen neben der Standardgrammatik ECMAScript auch basic, extended, awk, grep und egrep zur Verfügung. Während die Optimierung der Instanzen vom Typ regulärer Ausdruck den Spezialisten adressiert, bietet std::regex_constants::icase die Möglichkeit, den regulären Ausdruck unabhängig von Groß- und Kleinschreibung (case insensitive) zu behandeln. Werden die Optimierungsflags oder auch Modifikatoren der Grammatik verwendet, muss die Syntax der Grammatik verbindlich angegeben werden. Alle drei Parameter werden an den Konstruktor von std::regex bzw. std::wregex über eine Bitmaske übergeben.

Nach der Theorie nun das einfache Listing 19.4, das die case sensitive und case insensitive Suche in einem String anwendet.

case.cpp

In Zeile 13 in Listing 19.4 wird der reguläre Ausdruck definiert, der für die Suche in std::string theQuestion (Zeile 10) verwendet werden soll. In Zeile 14 ist der äquivalente reguläre Ausdruck ohne den Einsatz eines Raw-String-Literals dargestellt. Das erste Objekt vom Typ regulärer Ausdruck wird case sensitive instanziiert und in Zeile 25 angewandt. Im Gegensatz dazu wird das zweite Objekt über die Bitmaske std::regex_constants::ECMAScript|std::regex_constants::icase in Zeile 33 parametrisiert, sodass die Suche case insensitive ausgeführt wird. Abbildung 19.3 zeigt die Ausführung des Programms.

Weitere Elementfunktionen

Objekte vom Typ std::regex und std::wregex bieten noch weitere Funktionen an. Das Tauschen (swap) des Inhalts zweier Objekte wird in C++11 unterstützt. Seine Lokalisierungseigenschaften lassen sich ändern (imbue) und ausgeben (getloc). Dies ist auch für die Flags (flags) möglich. Weiter ist es möglich, die Anzahl der Erfassungsgruppen des regulären Ausdrucks, den das Objekt hält, durch die Elementfunktion (mark_count) abzufragen.

Aufgabe 19-2

Der Blick in die Tiefe.

In dem Buch »The C++ Standard Library Extensions« (Becker, The C++ Standard Library Extension, 2006) lässt Pete Becker in Kapitel 16 keine Frage zu den weiteren Details, insbesondere auch zur Ausnahmebehandlung der Objekte vom Typ reguläre Ausdrücke, offen.

Analyse des Suchergebnisses mit match_results

std::match_results stellt das Ergebnis eines std::regex_match- oder std::regex_search-Aufrufs zur Verfügung. Dabei ist std::match_results ein sequenzieller Container, der seine einzelnen Erfassungsgruppen als std::sub_match-Objekte zurückgibt. std::sub_match ist eine Sequenz von Zeichen.

Damit lässt sich komfortabel in Listing 19.5 über die Erfassungsgruppen und die Zeichenketten der Erfassungsgruppen iterieren.

iterate.cpp

Listing 19.5 beginnt vertraut. In Zeile 15 wird ein regulärer Ausdruck für IP4-Adressen erklärt, der in Zeile 18 an das Objekt vom Typ regulärer Ausdruck übergeben wird. Spannender wird es in dem if-Block (Zeilen 24 bis 40). std::regex_match prüft, ob der Eingabestring privateAddress eine gültige IP4-Adresse ist. Ist das der Fall, wird der if-Block ausgeführt und in der for-Schleife (Zeilen 26 bis 38) jeder Erfassungsgruppe ausgegeben. Dies ist aber nur die Iteration über alle Erfassungsgruppen. In Zeile 33 wird über die Zeichen jeder Erfassungsgruppe iteriert. Um den Unterschied optisch zu verdeutlichen, stellt die Lambda-Funktion jedes Zeichen hexadezimal dar.

In Abbildung 19.4 ist die Ausgabe des Programms zu sehen.

Noch eine Feinheit: Während das Suchergebnis smatch vom Typ std::match_results ist, ist die Erfassungsgruppe cap vom Typ std::sub_match. Dies erklärt, warum die Objekte smatch und cap verschiedene Interfaces anbieten. Um den Überblick zu behalten, sind in den zwei folgenden Tabellen die wichtigsten Funktionen des Datentyps std::match_results (Tabelle 19.8) sowie des Datentyps std::sub_match (Tabelle 19.9) dargestellt.

match_result-Funktionen

Tabelle 19.8 std::match_results-Funktionen

Funktion

Beschreibung

smatch.swap(smatch1)

std::swap(smatch,smatch1)

Tausche smatch und smatch1.

smatch[i]

Gibt die i-te Erfassungsgruppe vom Typ std::sub_match zurück.

smatch.position(i)

Gibt den Offset vom Anfang des Suchstrings bis zum Beginn der i-ten Erfassungsgruppe zurück.

Default-Wert i= 0.

smatch.length(i)

Gibt die Länge der i-ten Erfassungsgruppe zurück.

Default-Wert i= 0.

smatch.str(i)

Gib die Zeichenkette der i-ten Erfassungsgruppe als String zurück.

Default-Wert i= 0.

smatch.format( ... )

Ermöglicht die Formatierung der Eingabestrings mithilfe des Suchergebnisses smatch.

(Genaueres hierzu folgt in Kapitel 19 im Abschnitt „Formatieren mit regex_replace und match_results.format“.)

smatch.prefix()

Gibt ein sub_match-Objekt zurück, das die Zeichenkette vor dem Suchergebnis (smatch[0]) referenziert.

smatch.suffix()

Gibt ein sub_match-Objekt zurück, das die Zeichenkette nach dem Suchergebnis (smatch[0]) referenziert.

smatch.begin()

Gibt einen Iterator auf die erste Erfassungsgruppe zurück.

smatch.end()

Gibt die Zeichenkette nach dem Suchergebnis (smatch[0]) zurück.

smatch.size()

Gibt die Anzahl der Erfassungsgruppen zurück.

smatch.max_size()

Gibt die Länge der längsten Erfassungsgruppe zurück.

smatch.empty()

Gibt true zurück, falls smatch keine Erfassungsgruppe besitzt, sonst false.

smatch.get_allocator()

Gibt eine Kopie des Allokators für smatch zurück.

smatch == smatch1

smatch != smatch1

Vergleicht die Suchergebnisse smatch und smatch1.

sub_match-Funktionen

typdef für match result

Für die Zeichentypen char und wchar_t gibt es sowohl für std::match_results als auch für std::sub_match Typsynonyme, die das Leben eines Programmierers einfacher machen.

iterateSolution.cpp

Aufgabe 19-3

Implementieren Sie Listing 19.5 für den Charaktertyp const char*.

Das Programm iterate.cpp aus Listing 19.5 ist für den Zeichentyp std::string implementiert. Ein paar Modifikationen, und es kann auf const char* angewandt werden.

Exakte Treffer mit regex_match

std::regex_match soll helfen, die Frage zu beantworten: Entspricht meine Zeichenkette dem regulären Ausdruck? Da sich diese einfache Frage mit Ja oder Nein beantworten lässt, kann std::regex_match ohne ein Suchergebnis std::match_results (Listing 19.5) verwendet werden.

Genau diese Anwendung zeigt Listing 19.8.

regexMatch.cpp

In Listing 19.8 wird std::regex_match in Zeile 19 mit einem Zeiger auf const char, in Zeile 25 mit einem std::string und in Zeile 31 mit zwei Iteratoren aufgerufen. Alle drei Aufrufe ergeben das erwartete Ergebnis (Abbildung 19.5).

Neben diesen drei überladenen Versionen des Funktions-Templates std::regex_match existieren noch drei weitere Varianten, die ein Suchergebnis vom Typ std::match_results erwarten. Diese sind bereits in Listing 19.5 angewandt worden.

Weitere Flags in regex_constants

regexMatchSolution.cpp

Aufgabe 19-4

Erweitern Sie die Zahlenerkennung in Listing 19.8 um die Exponentialschreibweise.

Listing 19.8 ist nur die halbe Lösung des Problems. Literale der Form »-3.44E+4« sind für das Programm keine Zahlen. Erweitern Sie daher den regulären Ausdruck so, dass er Zahlen in der wissenschaftlichen Notation auch erkennt.

Testen Sie Ihren Algorithmus mit den Zahlen 2011, 3.14159, -3.44E+4 und -1.02E-4.

Suchen mit regex_search

regex_search

Die Anwendung von std::regex_search ist der von std::regex_match sehr ähnlich. Zu jeder der sechs Varianten von std::regex_match existiert eine Variante von std::regex_search, die die gleiche Signatur besitzt. Während std::regex_match entscheidet, ob eine Zeichenkette einem gegebenen regulären Ausdruck entspricht, sucht std::regex_search eine dem regulären Ausdruck entsprechende Zeichenkette in dem Eingabetext.

In Listing 19.2 und Listing 19.3 haben wir schon std::regex_search zusammen mit einem Suchergebnis im Einsatz gesehen. Was aber noch fehlt, ist der Einsatz von verschiedenen Zeichentypen. In Listing 19.9 wird ein regulärer Ausdruck verwendet, um die Zeit aus der Zeichenkette zu extrahieren. Die Datentypen const char*, std::string, cont wchar_t* und std::wstring werden verwendet.

regexSearch.cpp

01 #include <regex>
02
03 #include <iostream>
04 #include <string>
05
06 int main(){
07
08   std::cout << std::endl;
09
10   // regular expression holder for time
11   std::regex crgx("([01]?[0-9]|2[0-3]):[0-5][0-9]");
12
13   // const char*
14   std::cout << "const char*" << std::endl;
15   std::cmatch cmatch;
16
17   const char* ctime{"Now it is 23:10."};
18
19   if (std::regex_search(ctime,cmatch,crgx)){
20
21      std::cout << ctime << std::endl;
22      std::cout << "Time: " << cmatch[0] << std::endl;
23
24    }
25
26   std::cout << std::endl;
27
28   // std::string
29   std::cout << "std::string" << std::endl;
30   std::smatch smatch;
31
32   std::string stime{"Now it is 23:25."};
33   if (std::regex_search(stime,smatch,crgx)){
34
35     std::cout << stime << std::endl;
36     std::cout << "Time: " << smatch[0] << std::endl;
37
38   }
39
40   std::cout << std::endl;
41
42   // regular expression holder for time
43   std::wregex wrgx(L"([01]?[0-9]|2[0-3]):[0-5][0-9]");
44
45   // const wchar_t*
46   std::cout << "const wchar_t* " << std::endl;
47   std::wcmatch wcmatch;
48
49   const wchar_t* wctime{L"Now it is 23:47."};
50
51   if (std::regex_search(wctime,wcmatch,wrgx)){
52
53        std::wcout << wctime << std::endl;
54        std::wcout << "Time: " << wcmatch[0] << std::endl;
55
56   }
57
58   std::cout << std::endl;
59
60   // std::wstring
61   std::cout << "std::wstring" << std::endl;
62   std::wsmatch wsmatch;
63
64   std::wstring  wstime{L"Now it is 00:03."};
65
66   if (std::regex_search(wstime,wsmatch,wrgx)){
67
68     std::wcout << wstime << std::endl;
69     std::wcout << "Time: " << wsmatch[0] << std::endl;
70
71   }
72
73   std::cout << std::endl;
74
75 }
Listing 19.9 std::regex_search mit verschiedenen Zeichentypen

Listing 19.9 zeigt den Umgang mit den verschiedenen Datentypen const char* (Zeilen 13 bis 24), std::string (Zeilen 28 bis 38), const wchar_t* (Zeilen 45 bis 56) und std::wstring (Zeilen 60 bis 71). Werden alle Parameter von std::regex_search mit dem gleichen Zeichentyp verwendet, klappt es auch mit der Ausgabe (Abbildung 19.6).

Zum Abschluss will ich den Umgang mit std::wstring in Listing 19.9 explizit auf den Punkt bringen. Für das richtige Verarbeiten von std::regex_search ist es erforderlich, dass der reguläre Ausdruck wrgx (Zeile 43), das Suchergebnis wsmatch (Zeile 62), die zu untersuchende Zeichenkette wstime (Zeile 64) und der Ausgabekanal std::wcout (Zeile 68) vom gleichen Datentyp sind.

repetitiveSearch

Aufgabe 19-5

Verwenden Sie std::regex_search für das wiederholte Suchen einer Zeichenkette in einem Eingabestring.

Suchen Sie alle Ganzzahlen in einem Eingabestring und geben Sie diese aus. Bjarne Stroustrup über C++11 als möglichen Eingabestring: »This is close to the final draft international standard formally accepted by a 21-0 national vote in August 2011. Unless the ISO bureaucracy is unusually slow, the standard will be officially issued this year so that it will be referred to as C++11 or C++2011.« (Stroustrup, 2011).

Ersetzen mit regex_replace

Während die Algorithmen std::regex_match und std::regex_search mächtige Helfer sind, um einen String in einem Eingabestring mithilfe eines regulären Ausdrucks zu identifizieren, erlaubt es std::regex_replace, diesen identifizierten String durch einen neuen String zu ersetzen.

In Listing 19.10 wird jeweils std::string in zwei Schritten modifiziert zurückgegeben.

regexReplace.cpp

Das Programm in Listing 19.10 versucht, mit der für einen Autor undankbaren Situation umzugehen, dem neuen Kind einen Namen geben zu müssen. Durch std::regex_replace wird in den Zeilen 21 und 28 der bestehende String durch einen neuen ersetzt. So bewirkt der Aufruf von std::regex_replace(unofficialStandardName,rgxCpp,newCppName), dass jeder Teilstring in unofficialStandardName, der dem Muster rgxCpp entspricht, durch den String newCppName ersetzt wird.

In der Hoffnung, dass die Ausgabe in Abbildung 19.7 korrekt ist:

Verallgemeinertes regex_replace

Neben dem dargestellten Funktions-Template std::regex_replace, das das Ergebnis der Ersetzung als Rückgabewert zurückgibt, bietet C++11 noch eine allgemeinere Form des Funktions-Templates an. Dieses Funktions-Template arbeitet in bekannter STL-Manier auf Iteratoren.

Listing 19.11 stellt den std::regex_replace-Einsatz von Listing 19.10 dem verallgemeinernden Iteratorenansatz gegenüber. Der Einfachheit halber sind die Variablennamen deutlich verkürzt.

In der Iterator-Version in Listing 19.11 wird in Zeile 10 eine Variable newName2 angelegt, in der das Ergebnis gespeichert werden soll. Der Ausdruck in Zeile 11, in Prosa übersetzt, lautet: Gehe durch den Bereich unoff.begin() bis unoff.end(), indem du alle Treffer von rgx durch cpp ersetzt und an newName2 hinten anhängst (std::back_inserter(newName2)).

Zwei Flags: format_no_copy, format_first_only

std::regex_replace kann über zwei Flags noch weiter parametrisiert werden. So bewirkt std::regex_constants::format_no_copy, dass lediglich die Teilstrings in den Ergebnisstring kopiert werden, die den regulären Ausdruck erfüllen. Soll nur der erste Teilstring kopiert werden, lässt sich dieses Verhalten über das Flag std::regex_constants::format_first_only steuern.

replaceText.cpp

Aufgabe 19-6

Schieben Sie alle Treffer auf einen neuen Vektor.

Verwenden Sie std::regex_replace und das Flag std::regex_constants::format_not_copy, um die Zahlen in dem String auf einen anderen Vektor zu schieben. Geben Sie den neuen Vektor aus.

Ein möglicher Eingabestring: »This is close to the final draft international standard formally accepted by a 21-0 national vote in August 2011. Unless the ISO bureaucracy is unusually slow, the standard will be officially issued this year so that it will be referred to as C++11 or C++2011.« (Stroustrup, 2011).

Nutzen Sie dabei aus, dass in dem Eingabestring nur Ganzzahlen vorkommen. Dabei bezeichnet »$&« den gesamten Treffer im Ersetzungstext (Tabelle 19.10).

replaceTextFirst.cpp

Aufgabe 19-7

Ersetzen Sie den ersten Treffer durch einen neuen Text.

Die E-Mail ist leider nicht mehr aktuell. Ersetzen Sie die erste Zahl durch 2012.

»We happily announce to you the draw of the Euro – Afro Asian Sweepstake Lottery International programs held on the first of May 2004 in Dakar Senegal.Your e-mail address attached to ticket number: 564-75600545-188 with Serial number 5388/02 drew the lucky numbers: 31-6-26-13-35-7, which subsequently won you the lottery in the 2nd category.\n\n CONGRATULATIONS!!!«

Formatieren mit regex_replace und match_results.format

Um in Listing 19.10 den endgültigen String zu erhalten, waren zwei Iterationen notwendig. Das ist umständlich, verlangt doch jede Ersetzung eine Iteration. Erfassungsgruppen in Kombination mit der Elementfunktion std::match_results.format erlauben dies in einem Schritt. Dabei wird das Problem umformuliert. Statt in einen bestehenden String einen Teilstring über mehrere Iterationen hinweg zu verändern, wird ein Formatstring mit Platzhaltern vorgegeben, in den die neuen Werte eingesetzt werden. Dieser einfache Anwendungsfall lässt sich auch noch mit dem alten Bekannten std::regex_replace umsetzen.

regexFormat.cpp

In Listing 19.12 wird die erste Ausgabe mit std::regex_replace (Zeile 22), die zweite Ausgabe mit std::match_results.format (Zeile 31) formatiert. Durch »$i« kann in dem Formatstring standardText (Zeile 19) die i-te Erfassungsgruppe referenziert werden. Dabei beschreibt regValues{"(.*),(.*)"} in Zeile 17 die erste und zweite Erfassungsgruppe der Strings unofficial (Zeile 14) und official (Zeile 15).

Die formatierte Ausgabe ist in Abbildung 19.8 zu sehen.

Die weiteren Format-Escape-Sequenzen der ECMAScript-Grammatik sind in Tabelle 19.10 zusammengefasst.

Mächtigkeit von match_results

Auch wenn der Einsatz des Suchergebnisses std::match_results aufwendiger ist als der von std::regex_replace, so bietet er doch zwei Vorteile.

In Analogie zu std::regex_replace (Listing 19.11) gibt es die Funktion std::match_results.format in zwei Varianten. Die einfache Variante, die in Listing 19.12 angewandt wurde, gibt einen String zurück. Die allgemeinere Form setzt Iteratoren voraus.

regexFormatStandard.cpp

Aufgabe 19-8

Gewappnet für die Zukunft.

Im mittlerweile bekannten Text von Bjarne Stroustrup (Stroustrup, 2011) sind die Jahreszahlen als Variablen vorgehalten.

»This is close to the final draft international standard formally accepted by a 21-0 national vote in August $1. Unless the ISO bureaucracy is unusually slow, the standard will be officially issued this year so that it will be referred to as C++$2 or C++$1.«

Ein einfaches std::regex_replace löst das Problem. In erster Annäherung soll $1 den Wert 2011 und $2 den Wert 11 besitzen.

Mit std::regex_iterator und std::regex_token_iterator bietet die C++11-Bibliothek zwei mächtige Werkzeuge an, um über Vorkommen eines Teilstrings in einem String zu iterieren. Dabei erlaubt std::regex_iterator die Iteration über die Suchergebnisse std::match_results jedes Teilstrings, der einem regulären Ausdruck entspricht. Im Gegensatz hierzu geht die Iteration bei std::regex_token_iterator noch weiter ins Detail. Nicht nur über das Such-ergebnis, sondern auch über die einzelnen Erfassungsgruppen wird iteriert. Die Art der Iteration lässt sich bei ihm durch Indizes genauer steuern.

Wie oft kommt ein Wort in einem Text vor? Dieser Klassiker aller Programmieraufgaben lässt sich in C++11 annähernd mit der Leichtigkeit einer Interpretersprache wie Python programmieren (Listing 19.12).

regexIterator.cpp

Das Programm in Listing 19.12 sollte zum größten Teil vertraut sein. In Zeile 18 wird ein Iterator definiert. In diesem konkreten Fall iteriert er über alle Wörter von text.begin() bis text.end(), die dem regulären Ausdruck wordReg (Zeile 15) entsprechen. Diese Wörter werden in den Zeilen 25 und 26 in die neue C++11-Hashtabelle eingefügt, und der Zähler wird erhöht. Dabei beendet wordItEnd die Iteration. wordItBegin ist vom bekannten Typ std::match_results, sodass die String-Repräsentation der ersten Erfassungsgruppe mittels wordItBegin->str() zur Verfügung steht. Sind alle Wörter durchlaufen, kann das Ergebnis in Zeile 29 kompakt ausgegeben werden.

Abbildung 19.9 zeigt das Ergebnis.

Eines sei nochmals explizit erwähnt. Jeder Teilstring, der dem regulären Ausdruck entspricht, wird über den Typ std::match_results zur Verfügung gestellt. Damit lässt sich jedes einzelne Suchergebnis mit der Mächtigkeit der Funktionen aus Tabelle 19.8 weiterverarbeiten.

Diese Mächtigkeit zeigt das folgende Beispiel. Darin wird auf jede Erfassungsgruppe direkt Bezug genommen.

regexIteratorIndex.cpp

Der reguläre Ausdruck in Listing 19.14 in Zeile 15 ist relativ schwierig zu lesen. Ein Suchergebnis besteht aus dem Vornamen (\w+) und dem Nachnamen des Autors (\w+), dem Titel ([\w\s\+]*) und dem Erscheinungsdatum (\d{4}) des Werks. Diese Einträge, die zugleich die Erfassungsgruppen sind, sind durch Kommata (,) getrennt. Der std::sregex_token_iterator in Zeile 18 wendet diesen regulären Ausdruck an. In den Zeilen 21 bis 29 wird das Ergebnis optisch aufbereitet, indem jede Erfassungsgruppe mit ihrem Index und ihrem Wert ausgegeben wird (Listing 19.14).

In bekannter C++-Tradition gibt es für die Standardzeichentypen in C++11 die bekannten Synonyme, um Schreibarbeit zu sparen.

regexIteratorSolution.cpp

Aufgabe 19-9

Bestimmen Sie, wie oft ein Wort in einem Text vorkommt.

Listing 19.13 ist der erste Schritt zur Antwort auf die Frage: Welches Wort kommt am häufigsten im Text vor? Bestimmen Sie die Häufigkeit der Wörter in einem Text und geben Sie diese nach ihrer Häufigkeit sortiert aus.

countAlphabet.cpp

Aufgabe 19-10

Bestimmen Sie, wie oft ein Buchstabe in einem Text vorkommt.

In der kleinen Variation von Aufgabe 19-9 soll die Häufigkeit der Buchstaben des Alphabets ermittelt werden – und dies ohne Berücksichtigung der Klein- oder Großschreibung. Geben Sie die Buchstaben nach ihrer Häufigkeit sortiert aus.

regex_token_iterator

std::regex_token_iterator iteriert über die Suchergebnisse und deren Erfassungsgruppen. Darüber hinaus ist insbesondere konfigurierbar, welche Komponenten einer Erfassungsgruppe angesprochen werden sollen. Dazu besitzt std::regex_token_iterator Konstruktoren, die im Gegensatz zu std::regex_iterator mit Indizes verwendet werden können. Diese Indizes bewirken, dass nur über die entsprechenden Erfassungsgruppen iteriert wird. Ein Index kann die Form einer einfachen Zahl oder auch eines Vektors besitzen. Die Zahl -1 hat eine besondere Bedeutung, da der Teilstring zwischen den Suchergebnissen ausgegeben wird.

Dieser besondere Anwendungsfall soll Variationen von Listing 19.14 mit Indizes in Listing 19.16 verdeutlichen.

regexTokenIterator.cpp

01 #include <regex>
02
03 #include <iostream>
04 #include <string>
05 #include <vector>
06
07 int main(){
08
09   std::cout << std::endl;
10
11   // a few books
12   std::string text{
       "Pete Becker,The C++ Standard Library Extensions,2006:
        Nicolai Josuttis,The C++ Standard Library,1999:
        Andrei Alexandrescu,Modern C++ Design,2001"};
13
14   // regular expression for a book
15   std::regex regBook(
       R"((\w+)\s(\w+),([\w\s\+]*),(\d{4}))");
16
17   // get all books from text
18   std::sregex_token_iterator bookItBegin(
       text.begin(),text.end(),regBook);
19   const std::sregex_token_iterator bookItEnd;
20
21   std::cout << "##### std::match_results ######"
               << "\n\n";
22   while ( bookItBegin != bookItEnd){
23     std::cout << *bookItBegin++ << std::endl;
24   }
25
26   std::cout << "\n\n"
               << "##### last name, date of publication ######"
               << "\n\n";
27
28   // get all last name and date of publication for the entries
29   std::sregex_token_iterator bookItNameIssueBegin(
       text.begin(),text.end(),regBook,{{2,4}});
30   const std::sregex_token_iterator bookItNameIssueEnd;
31   while ( bookItNameIssueBegin != bookItNameIssueEnd){
32       std::cout << *bookItNameIssueBegin++ << ", ";
33       std::cout << *bookItNameIssueBegin++ << std::endl;
34   }
35
36   // regular expression for a book, using negativ search
37   std::regex regBookNeg(":");
38
39   std::cout << "\n\n"
       << "##### get each entry, using negative search  ######"
               << "\n\n";
40
41   // get all entries, only using ":" as regular expression
42   std::sregex_token_iterator bookItNegBegin(
       text.begin(),text.end(),regBookNeg,-1);
43   const std::sregex_token_iterator bookItNegEnd;
44    while ( bookItNegBegin != bookItNegEnd){
45        std::cout << *bookItNegBegin++ << std::endl;
46    }
47
48   std::cout << std::endl;
49
50 }
Listing 19.16 std::regex_token_iterator mit verschiedenen Indizes

Für das Verständnis von Listing 19.16 ist die Ausgabe des Programmlaufs sehr hilfreich (Abbildung 19.11), die in drei Drittel geteilt ist.

Wird std::regex_token_iterator ohne einen Index verwendet (Zeile 18), zeigt er ähnlich wie std::regex_iterator jeden Eintrag an. Die erste und die dritte Teilausgabe sind identisch. Bemerkenswert ist, dass dies durch verschiedene reguläre Ausdrücke erreicht wurde. Während die erste Teilausgabe durch den bekannten regulären Ausdruck "(\w+)\s(\w+),([\w\s\+]*),(\d{4})" erzeugt wurde, resultiert die letzte aus dem regulären Ausdruck ":". Der Trick besteht darin, alle Teilstrings zu suchen, die nicht diesem regulären Ausdruck entsprechen. Das sind genau die gesuchten Einträge. Für diese negative Suche benötigt der Konstruktor von std::regex_token_iterator den Index -1 (Zeile 42). Die Ausgabe in der Mitte von Abbildung 19.11 besteht nur aus den Nachnamen und dem Veröffentlichungsdatum des Werks. Dazu wird der Konstruktor mit der Initialisiererliste {2,4} gefüttert (Zeile 29). Um den Zeilenumbruch für die dargestellten Erfassungsgruppen zu unterdrücken, werden zwei Einträge in einem Schleifendurchlauf ausgegeben (Zeilen 32 und 33).

Für den einfachen Umgang mit dem Standardzeichentyp hält C++11 wieder die bekannten Typsynonyme vor.

split.cpp

Aufgabe 19-11

Schreiben Sie die split-Funktion in C++11.

Die Python-Funktion str.split(sep), auf einen String str angewandt, gibt eine Liste von Strings zurück, die mittels sep getrennt werden.

Das geht auch in C++11. Schreiben Sie eine Funktion split, die einen std::string und ein Trennzeichen als Argument annimmt und als Ergebnis einen std::vector<std::string> zurückgibt. Wenden Sie die Funktion an.

Die neue Type-Traits-Bibliothek in C++11 ist ein mächtiges Werkzeug für den Bibliotheksautor, erlaubt sie es doch, Typabfragen und Typvergleiche, ja sogar Typtransformationen zur Übersetzungszeit auszuführen. Kosten für die Laufzeit des C++-Programms sind nicht vorhanden, da der resultierende Code bereits zur Laufzeit vorliegt. Mit dieser Bibliothek verliert Template-Metaprogramming viel von seiner Magie und setzt keinen Expertenstatus in C++ voraus. Konsequenterweise werden die Funktionen, die zur Übersetzungszeit den C++-Sourcecode erzeugen, als Metafunktionen bezeichnet. Der Name Metafunktion trifft natürlich nicht nur auf die Funktionen der Type-Traits-Bibliothek, die unter der Decke Klassen-Templates sind, zu. Dieser Name trifft auf alle Klassen-Templates zu, die zur Übersetzungszeit wie Funktionen angewandt werden können, um den resultierenden C++-Sourcecode zu erzeugen.

Verfolgtes Ziel

Welche Ziele werden mit der Type-Traits-Bibliothek verfolgt? Die Antwort ist schnell parat:

Optimierung, da aufgrund von Typeigenschaften die schnellere Implementierung, Korrektheit, da aufgrund von Typeigenschaften die richtige Implementierung eines Algorithmus ausgewählt werden kann.

Anwendungen

Wer Anwendungen zur neuen Type-Traits-Bibliothek sucht, der wird in der Boost-Bibliothek fündig. Diese Beispiele lassen sich direkt auf C++11 übertragen, da die Boost-Implementierung Grundlage für die C++11-Type-Traits-Bibliothek ist. So finden sich Beispiele für:

Ermöglicht werden diese Optimierungen und Modifikationen durch die Type-Traits-Bibliothek. Deren Funktionen fragen die Typen zur Übersetzungszeit ab, ob diese hinreichende Bedingungen für die Optimierung anbieten. So setzen std::copy und std::fill unter anderem voraus, dass die Typen trivial zuweisbar (std::is_trivially_copy_assignable in C++11) sein sollen. Sind die Typen hinreichend einfach, können C-Funktionen für die Algorithmen angewandt werden, die die Operationen bitweise und nicht elementweise durchführen. Auf den Destruktoraufruf kann nur verzichtet werden, wenn dieser trivial ist (std::is_trivially_destructible in C++11). Die Bedingungen an std::iter_swap sind schon strenger. Die zu tauschenden Elemente müssen vom gleichen Typ und Referenzen (std::is_reference in C++11) sein. Dadurch lässt sich die std::iter_swap-Version anwenden.

Am einfachsten lässt sich der Einsatz der Type-Traits-Bibliothek an einem Beispiel erläutern. Der Klassiker hierzu ist eine optimierte std::copy-Implementierung, die die Struktur in Listing 19.18 besitzen kann.

copy.cpp

01 // because of memcpy
02 #include <string.h>
03
04 #include <iostream>
05 #include <type_traits>
06
07
08 namespace my{
09
10   template<typename I1, typename I2, bool b>
11   I2 copy_imp(I1 first, I1 last, I2 out,
                 const std::integral_constant<bool, b>&){
12
13     while(first != last){
14       *out = *first;
15       ++out;
16       ++first;
17     }
18
19     std::cout << "elementwise." << std::endl;
20     return out;
21
22   }
23
24   template<typename T>
25   T* copy_imp(const T* first, const T* last, T* out,
                 const std::true_type&){
26
27     memcpy(out, first, (last-first)*sizeof(T));
28     std::cout << "bitwise." << std::endl;
29     return out+(last-first);
30
31   }
32
33   template<typename I1, typename I2>
34   I2 copy(I1 first, I1 last, I2 out){
35
36     typedef typename std::iterator_traits<I1>::value_type
         value_type;
37     // standard name commented out
38     //return copy_imp(first, last, out,
         std::is_trivially_copy_assignable<value_type>());
39     return copy_imp(first, last, out,
         std::has_trivial_copy_assign<value_type>());
40
41   }
42 }
43
44 const int arraySize = 1000;
45
46 // intialize all elements to 0
47 int intArray[arraySize] = {0,};
48 int intArray2[arraySize]={0,};
49
50 int* pArray = intArray;
51 const int* pArray2 = intArray2;
52
53 int main(){
54
55   std::cout << std::endl;
56
57   std::cout << "Copying pArray ";
58
59   my::copy(pArray2, pArray2 + arraySize, pArray);
60
61   std::cout << "\n" << "Copying intArray ";
62
63   my::copy(intArray2, intArray2 + arraySize, intArray);
64
65   std::cout << std::endl;
66
67 }
Listing 19.18 Optimiertes Kopieren

Einsatz in der STL

Tatsächlich wird in der Implementierung des std::copy-Algorithmus des GCC 4.7 eine Kopierfunktion verwendet, die gegebenenfalls auf die C-Funktion memmove zurückgreift. Diese kann überlappende Speicherbereiche im Gegensatz zu memcpy (Listing 19.18, Zeile 27) verwenden. Das verrät die Dokumentation der Funktion std::copy im Sourcecode des STL-Algorithmus: This inline function will boil down to a call to @c memmove whenever possible.

In Abbildung 19.13 ist zu sehen, dass pArray2 in Zeile 50 bitweise, intArray2 in Zeile 54 elementweise kopiert wird.

Wie funktioniert dieser Polymorphismus zur Übersetzungszeit? Um die performantere Implementierung von copy_imp in Zeile 24 zu verwenden, müssen die Argumente drei Bedingungen erfüllen. Diese Bedingungen, die sich alle in der Signatur der Funktion wiederfinden, sind insbesondere die, die memcpy fordert, um angewendet werden zu können.

Den eigentlichen Dispatch vollzieht das Funktions-Template copy in Zeile 33. In Zeile 36 wird durch typedef typename std::iterator_traits<I1>::value_type value_type der Typ der Containerelemente bestimmt, um ihn anschließend im Rückgabewert der Funktion in dem Klassen-Template std::is_trivially_copy_assignable<value_type>() zu nutzen. Ist dieses Klassen-Template von std::true_type abgeleitet worden, findet der Dispatch auf die spezielle Implementierung in Zeile 33, sonst auf die generische Implementierung in Zeile 24 statt.

Zugegeben, das war schwer verdauliche Kost. Zumeist sind die Funktionen der Type-Traits-Bibliothek einfach anzuwenden.

Die primären Typkategorien sind vollständig und schließen sich gegenseitig aus. Jeder Datentyp kann genau nur einer Kategorie angehören. Dabei ist das Ergebnis der Abfrage unabhängig davon, ob der Typ als const oder volatile deklariert ist. C++11 kennt 13 verschiedene primäre Typkategorien, die sich über ein Prädikat abfragen lassen.

Die zusammengesetzten Typkategorien bauen auf den primären Typkategorien auf. Abfragen an sie ignorieren in Analogie zur primären Typkategorie, ob diese als const oder volatile deklariert wurde. Ausgehend von den 13 primären Typkategorien, bietet die Type-Traits-Bibliothek sechs verschieden zusammengesetzte Typkategorien an, die im Listing 19.21 dargestellt sind.

Die Komposition der zusammengesetzten aus den primären Typkategorien ist in Tabelle 19.11 dargestellt.

Dabei ist die Tabelle 19.11 so zu lesen, dass

std :: is _ arithmetic <T>:: value == true genau dann zutrifft, wenn

  • std :: is _ floating _ point <T>== true oder

  • std :: is _ integral <T>:: value == true gilt.

Einzig std::is_compound::value<T>::value wird über das Komplement von std::is_fundamental<T>::value definiert.

Listing 19.22 zeigt die Introspektionsfähigkeit der primären und zusammengesetzten Typkategorien.

typeCategories.cpp

01 #include <iostream>
02 #include <string>
03 #include <type_traits>
04
05
06 // using Euclid's Algorithm
07 template<typename T>
08 T gcd(T a, T b){
09
10   static_assert(std::is_integral<T>::value,
                   "T should be an integral type!");
11
12   if( b == 0 ){
13     return a;
14   }
15   else{
16     return gcd(b, a % b);
17   }
18 }
19
20 int main(){
21
22   std::cout << std::endl;
23   std::cout << std::boolalpha << std::endl;
24
25   std::cout << "primary type categories" << std::endl;
26
27   std::cout << "std::is_void<void>::value: "
               << std::is_void<void>::value << std::endl;
28   std::cout << "std::is_integral<short>::value: "
               << std::is_integral<short>::value << std::endl;
29   std::cout << "std::is_floating_point<double>::value: "
               << std::is_floating_point<double>::value
               << std::endl;
30   std::cout << "std::is_array<int []>::value: "
               << std::is_array<int [] >::value << std::endl;
31   std::cout << "std::is_pointer<int*>::value: "
               << std::is_pointer<int*>::value << std::endl;
32   std::cout << "std::is_reference<int&>::value: "
               << std::is_reference<int&>::value << std::endl;
33   struct A{
34     int a;
35     int f(double){return 2011;}
36   };
37   std::cout << "std::is_member_object_pointer<int A::*>::value: "
               << std::is_member_object_pointer<int A::*>::value
               << std::endl;
38   std::cout << "std::is_member_function_pointer<int(A::*)
               (double)>::value: "
               << std::is_member_function_pointer<int (A::*)
               (double)>::value << std::endl;
39   enum E{
40     e= 1,
41   };
42   std::cout << "std::is_enum<E>::value: "
               << std::is_enum<E>::value << std::endl;
43   union U{
44     int u;
45   };
46   std::cout << "std::is_union<U>::value: "
               << std::is_union<U>::value << std::endl;
47   std::cout << "std::is_class<std::string>::value: "
               << std::is_class<std::string>::value
               << std::endl;
48   std::cout << "std::is_function<int * (double)>::value: "
               << std::is_function<int * (double)>::value
               << std::endl;
49
50   std::cout << std::endl;
51
52   std::cout << "compound type categories" << std::endl;
53
54   std::cout << "gcd(100,10)= " <<  gcd(100,10)
               << std::endl;
55   std::cout << "gcd(100,33)= " << gcd(100,33) << std::endl;
56   std::cout << "gcd(100,0)= " << gcd(100,0)  << std::endl;
57
58   /*
59   std::cout << gcd(3.5,4.0) << std::endl;
60   std::cout << gcd("100","10") << std::endl;
61   */
62
63   std::cout << std::endl;
64
65 }
Listing 19.22 Introspektion mit Typkategorien

Abbildung 19.14 zeigt die wortreichere Ausgabe des Programms in Listing 19.22. Während der Einsatz aller primären Typkategorien in der Ausgabe überflogen werden kann, verlangt der Einsatz der zusammengesetzten Typkategorie mehr Aufmerksamkeit. In den Zeilen 7 bis 18 wird der größte gemeinsame Teiler nach dem euklidischen Algorithmus (Euklidscher Algorithmus, 2011) berechnet. Der Algorithmus ist generisch formuliert. Mit der statischen Zusicherung static_assert(std::is_integral<T>::value, ...) wird sichergestellt, dass nur Ganzzahlen verwendet werden. Dies ist jedoch nur die halbe Wahrheit, denn ein Aufruf gcd(true,true) führt zum Abbruch der Übersetzung. In Zeile 16 wird in diesem Fall der gcd-Algorithmus mit den Datentypen gcd(bool&,int) instanziiert. Die gcd-Implementierung setzt aber voraus, dass beide Argumente den gleichen Typ besitzen. Sieht man von diesem Grenzfall ab, führt die Übersetzung der auskommentierten Zeilen 59 und 60 zur erwarteten Fehlermeldung in Abbildung 19.15.

Typeigenschaften bieten Zugang zu den wichtigeren Eigenschaften von Typen. Diese müssen von einer aktuellen Implementierung nicht implementiert werden, sodass eine Abfrage automatisch zu false evaluiert. In Listing 19.18 wurde die Typeigenschaft std::is_trivially_copy_assignable angewandt, um gegebenenfalls eine optimierte Version des std::copy-Algorithmus zu verwenden. In Listing 19.23 sind die vielen Prädikate zu den Datentypen aufgelistet.

template <class T> struct is_const;
template <class T> struct is_volatile;
template <class T> struct is_trivial;
template <class T> struct is_trivially_copyable;
template <class T> struct is_standard_layout;
template <class T> struct is_pod;
template <class T> struct is_literal_type;
template <class T> struct is_empty;
template <class T> struct is_polymorphic;
template <class T> struct is_abstract;

template <class T> struct is_signed;
template <class T> struct is_unsigned;

template <class T, class... Args> struct is_constructible;
template <class T> struct is_default_constructible;
template <class T> struct is_copy_constructible;
template <class T> struct is_move_constructible;

template <class T, class U> struct is_assignable;
template <class T> struct is_copy_assignable;
template <class T> struct is_move_assignable;

template <class T> struct is_destructible;

template <class T, class... Args> struct is_trivially_constructible;
template <class T> struct is_trivially_default_constructible;
template <class T> struct is_trivially_copy_constructible;
template <class T> struct is_trivially_move_constructible;

template <class T, class U> struct is_trivially_assignable;
template <class T> struct is_trivially_copy_assignable;
template <class T> struct is_trivially_move_assignable;
template <class T> struct is_trivially_destructible;

template <class T, class... Args> struct is_nothrow_constructible;
template <class T> struct is_nothrow_default_constructible;
template <class T> struct is_nothrow_copy_constructible;
template <class T> struct is_nothrow_move_constructible;

template <class T, class U> struct is_nothrow_assignable;
template <class T> struct is_nothrow_copy_assignable;
template <class T> struct is_nothrow_move_assignable;

template <class T> struct is_nothrow_destructible;
template <class T> struct has_virtual_destructor;
Listing 19.23 Typeigenschaften

Einzig die Eigenschaft nothrow im Namen eines Klassen-Templates scheint ein bisschen ungewohnt. Die Klassen unterscheiden sich von ihrem Namensvetter ohne die nothrow-Eigenschaft (siehe Exkurs: noexcept) nur darin, dass sie keine Ausnahme werfen. Im Gegensatz zu den Prädikaten aus Listing 19.23 geben die weiteren speziellen Typabfragen integrale Konstanten zurück. Das ist bei alignment_of die Speicherausrichtung eines Datentyps, bei rank die Anzahl der Dimensionen eines Arrays.

Typen transformieren

Mit der Type-Traits-Bibliothek lässt sich ein Typ auf dessen Eigenschaften abfragen, es lassen sich Typen vergleichen, und darüber hinaus kann ein Typ zur Übersetzungszeit modifiziert werden. Bevor die einzelnen Funktionen dargestellt werden, soll das Programm in Listing 19.24 seine Mächtigkeit demonstrieren.

typeTransformation.cpp

Die Spielereien aus Listing 19.24 rund um const int sind am einfachsten in der Ausgabe des Programms in Abbildung 19.16 zu verfolgen.

Ein paar Worte noch zu Listing 19.24. Die Aufrufe in den Zeilen 13 und 14 stellen den Standardfall dar. Für einen Typ wird mit der Funktion std::is_const evaluiert, ob er konstant ist. std::add_const in Zeile 19 bewirkt, dass ein int zur Übersetzungszeit in einen const int transformiert wird. Damit lassen sich neue Typen deklarieren, wie in den Zeilen 24 und 26 dargestellt. Dies kann man auf die Spitze treiben, indem die Aufrufe rekursiv in den Zeilen 32 und 33 verschachtelt werden. Bei der Abfrage, ob zwei Typen gleich sind, hilft auch wieder die Type-Traits-Bibliothek mit der Metafunktion std::is_same.

Um ein Gefühl für das umfassende Interface zum Transformieren der Datentypen mit der Type-Traits-Bibliothek zu bekommen, will ich die wichtigsten Funktionen schnell überfliegen.

const-volatile

Neben den bereits bekannten Funktionen std::remove_const und std::add_const ist auch die Eigenschaft volatile eines Datentyps (Listing 19.25) zur Übersetzungszeit veränderbar.

Referenzen

Diese Transformation trifft auf Referenzen und vorzeichenbehaftete Typen zu.

Vorzeichen

Die Modifikationen des Vorzeichens in Listing 19.27 benötigen keine std::remove_signed-Funktion, da diese Funktionalität durch std::make_unsigned angeboten wird. Komplizierter ist da schon die Transformation von Rvalues und Lvalues in Listing 19.26, da die Funktionen std::add_lvalue_reference und std::add_rvalue_reference die Referenz-Collapsing-Regeln (siehe Kernsprache: Rvalue-Referenzen) respektieren.

Zeiger

Hingegen ist die Modifikation der Zeigereigenschaft eines Datentyps deutlich direkter.

Array

Selbst die Anzahl der Dimensionen eines Arrays lässt sich zur Übersetzungszeit durch std::remove_extent um 1, durch std::remove_all_extents auf 0 reduzieren.

Aufgabe 19-12

Schmökern Sie in der Standard Template Library.

Die Metafunktionen der Type-Traits-Bibliothek werden in der Implementierung der Standard Template Library häufig verwendet. Schauen Sie zum Beispiel die Implementierung der Algorithmen std::copy, std::fill oder auch std::iter_swap an und versuchen Sie, das Muster hinter ihrer Verwendung zu verstehen.

removeConst.cpp

Aufgabe 19-13

Implementieren Sie RemoveConst.

Schreiben Sie eine Metafunktion, die von einem Datentyp die const-Eigenschaft entfernt. Stellen Sie den modifizierten Typ über ::type zur Verfügung. Stellen Sie die Funktionalität durch die Type-Traits-Metafunktion std::is_const sicher.

<random>

Die C++11-Zufallszahlenfunktionalität besteht aus zwei Teilen:

Sowohl für den Erzeuger als auch für den Verteiler der Zufallszahlen bietet C++11 verschiedene Implementierungen an. In bewährter Tradition folgt zuerst ein einführendes Beispiel (Listing 19.30).

distribution.cpp

01 #include <cstdlib>
02 #include <fstream>
03 #include <iostream>
04 #include <map>
05 #include <random>
06
07 static const int NUM=1000000;
08
09 void writeToFile(const char* fileName ,const std::map<int,int>& data ){
10
11   std::ofstream file(fileName);
12
13   if ( !file ){
14     std::cerr << "Could not open the file "
                 << fileName << ".";
15     exit(EXIT_FAILURE);
16   }
17
18   // print the datapoints to the file
19   for ( auto mapIt: data) file << mapIt.first << " "
       <<  mapIt.second << std::endl;
20
21 }
22
23 int main(){
24
25   std::random_device seed;
26
27   // default generator
28   std::mt19937 engine(seed());
29
30   // distributions
31
32   // min= 0; max= 20
33   std::uniform_int_distribution<> uniformDist(0,20);
34   // mean= 50; sigma= 8
35   std::normal_distribution<> normDist(50,8);
36   // mean= 6;
37   std::poisson_distribution<> poiDist(6);
38   // alpha= 1;
39   std::gamma_distribution<> gammaDist;
40
41   std::map<int,int> uniformFrequency;
42   std::map<int,int> normFrequency;
43   std::map<int,int> poiFrequency;
44   std::map<int,int> gammaFrequency;
45
46   for ( int i=1; i<= NUM; ++i){
47     ++uniformFrequency[uniformDist(engine)];
48     ++normFrequency[round(normDist(engine))];
49     ++poiFrequency[poiDist(engine)];
50     ++gammaFrequency[round(gammaDist(engine))];
51   }
52
53   writeToFile("uniform_int_distribution.txt",uniformFrequency);
54   writeToFile("normal_distribution.txt",normFrequency);
55   writeToFile("poisson_distribution.txt",poiFrequency);
56   writeToFile("gamma_distribution.txt",gammaFrequency);
57
58 }
Listing 19.30 Verschiedene Verteilungen im Einsatz

In Listing 19.30 werden mithilfe des Mersenne-Twister (Mersenne Twister, 2011) 1.000.000 Zufallszahlen erzeugt. Um mit einem zufälligen Startwert zu beginnen, muss der Zufallszahlenerzeuger mit der sogenannten seed in Zeile 28 initialisiert werden. Dieser Zufallszahlenstrom wird anschließend gleich-, normal-, Poisson- und gammaverteilt (Wahrscheinlichkeitsverteilung, 2011). Genauer gesagt, die 1.000.000 Zufallszahlen in Zeile 33 werden auf die natürlichen Zahlen 0 bis 20 gleichmäßig verteilt. In Zeile 35 wird die Normalverteilung oder auch Gaußverteilung mit dem Mittelwert 50 und der Standardabweichung 8 angewandt. In Zeile 37 kommt die Poisson-Verteilung mit dem Mittelwert 6 zum Einsatz, in Zeile 39 die Gammaverteilung. Die Ergebnisse der Normal- sowie der Gammaverteilung werden auf eine Ganzzahl gerundet. Für jede vorkommende natürliche Zahl wird ihre Häufigkeit gezählt (Zeilen 46 bis 51) und in eine Datei geschrieben (Zeilen 9 bis 21).

Die Ergebnisse sind in den nächsten vier Abbildungen dargestellt. Für die Optik sind die Datenpunkte mit einem Spline (Spline, 2011) interpoliert. Lediglich die Ergebnisse der Gleichverteilung (Abbildung 19.17) sind mit einer Geraden verbunden.

Jeder Zufallszahlenerzeuger gen vom Typ Generator muss vier Anfragetypen unterstützen:

Zufallszahlenerzeuger gibt es in C++11 in verschiedenen Ausprägungen. Sechs Templates und zehn Synonyme für die am häufigsten verwendeten Zufallszahlenerzeuger stehen zur Verfügung.

Die sechs Templates sind:

Aufbauend auf den Templates in Listing 19.31 hier die zehn Synonyme, die das Leben eines Programmierers leichter machen:

Eine kurze Bemerkung noch zu der default_random_engine in Listing 19.32. Es hängt von der Implementierung ab, welcher konkrete Zufallserzeuger verwendet wird. Die Entscheidungskriterien für die Implementierung sollen Performance, Größe und Qualität des Zufallszahlenerzeugers sein.

random_device

random_device ist ein nicht deterministischer Zufallszahlenerzeuger. Auf Systemen, auf denen dieser Zufallszahlenerzeuger nicht verfügbar ist, wird einer der vorhandenen Zufallszahlenerzeuger aus Listing 19.32 verwendet. Unter Linux wird random_device auf die spezielle Datei /dev/urandom abgebildet.

Zufallszahlenverteilung

Die Zufallszahlenverteiler in C++11 lassen sich in zwei Klassen aufteilen. Die diskreten Verteiler erzeugen Ganzzahlen, kontinuierliche Verteiler erzeugen Fließkommazahlen in C++11.

Sowohl bei der diskreten als auch bei der kontinuierlichen Verteilung kann der Anwender auf viele bekannte Verteilungen zurückgreifen.

Die Unterscheidung, ob eine Verteilung diskret oder kontinuierlich ist, lässt sich aus Listing 19.33 direkt ablesen. Lautet der Template-Parameter IntType, ist die Verteilung diskret. Im Fall von RealType ist die Verteilung kontinuierlich. Lediglich die Bernoulli-Verteilung bernoulli_distribution stellt eine Ausnahme von der Regel dar, da sie Wahrheitswerte zurückgibt.

Interface einer Verteilung

Eine Verteilung dist bietet nicht viel mehr an, als den nächsten Wert durch den Aufruf dist(gen) des Zufallszahlengenerators gen zu erhalten, den Zustand der Verteilung auf einen Ausgabestream out << dist zu schreiben und den Zustand einer Verteilung von einem Eingabestrom in >> dist zu lesen. Durch dist.reset() lässt sich der Zustand einer Verteilung zurücksetzen.

zeroOneSequence.cpp

Aufgabe 19-14: Zufallszahlen

Erzeugen Sie eine zufällige Folge von 0/1-Werten.

Zeitbibliothek

<chrono>

Die C++11-Zeitbibliothek besteht aus drei Komponenten: der Zeitdauer (duration), dem Zeitpunkt (time_point) und den Zeitgebern (system_clock, steady_clock und high_resolution_clock). Um die Begriffe, die sehr voneinander abhängen, besser unterscheiden zu können, finden Sie in dem folgenden Kasten die Definitionen.

In Listing 19.34 werden einige Features der neuen Zeitbibliothek angewandt.

timeBibliothek.cpp

01 #include <chrono>
02 #include <iostream>
03 #include <ratio>
04 #include <thread>
05
06 int main(){
07
08   std::cout << std::endl;
09
10   // get the actual time
11   std::chrono::system_clock::time_point
       start = std::chrono::system_clock::now();
12
13   // sleep 1000 * 1 Millisecond
14   for ( int i = 0; i <= 1000;++i){
15     std::this_thread::sleep_for(
         std::chrono::milliseconds(1));
16   }
17
18   std::chrono::duration<double> dur  =
       std::chrono::system_clock::now() - start;
19
20   std::cout << "sleeping 1000 times 1 Milliseconds take: "
               << dur.count() << std::endl;
21
22   auto begin= std::chrono::system_clock::now();
23
24   auto end = std::chrono::system_clock::now() +
       std::chrono::duration<double, std::ratio<1>>(0.5);
25
26   while (std::chrono::system_clock::now() < end);
27
28   std::chrono::duration<double> dur1  =
       std::chrono::system_clock::now() - begin;
29
30   std::cout << "busy waiting for half a second :"
               << dur1.count() << std::endl;
31
32   // typedef for minutes, seconds and milliseconds
33   typedef std::chrono::duration<long long,
                                   std::ratio<60>> minutes;
34   typedef std::chrono::duration<long long,
                                   std::ratio<1>> seconds;
35   typedef std::chrono::duration<long long,
                          std::ratio<1,1000>> milliseconds;
36
37   seconds sec(5);
38   minutes min(2);
39   milliseconds mil(10);
40
41   milliseconds milRes= min + sec + mil;
42
43   std::cout << "2 Minutes + 5 Seconds + 10 Milliseconds="
               << milRes.count() << " Milliseconds"
               << std::endl;
44
45   // typedef for hours
46   typedef std::chrono::duration<double,
                                   std::ratio<3600>> hours;
47   hours hourRes= milRes;
48
49   std::cout << "2 Minutes + 5 Seconds + 10 Milliseconds=  "
               << hourRes.count() << " Hours" << std::endl;
50
51   std::cout << std::endl;
52
53 }
Listing 19.34 Features der Zeitbibliothek

In Listing 19.34 wird in Zeile 11 der Startpunkt start mit der neuen Systemzeit in Zeile 18 verrechnet. Die eigentliche Arbeit findet in der for-Schleife in Zeile 14 statt, in der der aktuelle Thread 1.000 Mal für eine Millisekunde schlafen gelegt wird. In Abbildung 19.21 ist schön zu erkennen, dass durch das häufige Schlafenlegen und wieder Aufwachen ca. 13 % mehr Zeit beansprucht wird. Dieser Overhead ist beim busy waiting nicht vorhanden, denn die while-Schleife in Zeile 26 wird genau für eine halbe Sekunde ausgeführt (Abbildung 19.21). Sehr interessant ist das Rechnen mit verschiedenen Zeitdauern. Dazu wird in den Zeilen 33 bis 35 und 46 ein Synonym für Millisekunden, Sekunden, Minuten und Stunden definiert. Dabei bezeichnet das erste Argument des Templates den Datentyp, der die Zeitdauer hält, und das zweite Argument die Einheit. Während std::ratio<1> eine Sekunde definiert, steht std::ratio<60> für 60 Sekunden bzw. eine Minute. Erhält std::ratio wie im Fall von std::ratio<1,1000>zwei Argumente, steht dieser Ausdruck für 1/1000 Sekunde bzw. eine Millisekunde. Nach diesen Synonymen kann sehr elegant mit den Zeitdauern gerechnet werden. Wird der Zeitdauertyp als natürliche Zahl definiert und mit dem Datentyp Millisekunde (Zeile 41) angenommen, steht das Ergebnis normiert auf Millisekunden zur Verfügung. Aber auch im Stundenformat lässt sich das Ergebnis darstellen. Dazu ist es notwendig, als Repräsentation des Datentyps eine Fließkommazahl zu wählen und das Ergebnis in dem entsprechenden Typsynonym hours (Zeile 47) zu speichern. Diese verschiedenen Repräsentationen der gleichen Zeitdauer sind in Abbildung 19.21 schön zu sehen.

Die Zeitdauer ist die Differenz zwischen zwei Zeitpunkten. Sie besteht aus einem arithmetischen Typ und dem Zeittakt (tick period).

Ist der arithmetische Typ eine Fließkommazahl, unterstützt die Zeitdauer Bruchteile ihres Zeittakts. Der Zeittakt ist eine rationale Zahl vom neuen Typ std::ratio. Per Default ist 1 Sekunde die Grundeinheit des Zeittakts.

Vordefinierte Zeitdauern

Die wichtigsten Zeitdauern sind bereits in der Bibliothek als Synonyme definiert.

Hierzu ein paar Anmerkungen. Eine Zeitdauer muss mindestens +/–292 Jahre umfassen. Daraus resultieren die verschiedenen Anforderungen an die Größe der Datentypen in Listing 19.36. nano, micro und milli sind wiederum Aliase des Datentyps std::ratio.

Neben der Arithmetik mit Zeitdauern (Listing 19.34) ist die Definition eigener Zeittakte sicher ein interessanter Anwendungsbereich der Klasse duration. In Listing 19.38 werden die vordefinierten Zeitdauern ausgegeben. Darüber hinaus werden eine Schulstunde (45 Minuten) und eine Sekunde definiert.

duration.cpp

01 #include <chrono>
02 #include <iostream>
03 #include <ratio>
04
05 int main(){
06
07   std::cout << std::endl;
08
09   typedef std::chrono::duration<long long, std::ratio<1>>
       MySecondTick;
10
11   MySecondTick aSecond(1);
12
13   std::chrono::nanoseconds nano(aSecond);
14   std::cout << nano.count() << " nanoseconds" << std::endl;
15
16   std::chrono::microseconds micro(aSecond);
17   std::cout << micro.count() << " microseconds"
               << std::endl;
18
19   std::chrono::milliseconds milli(aSecond);
20   std::cout << milli.count() << " milliseconds"
               << std::endl;
21
22   std::chrono::seconds seconds(aSecond);
23   std::cout << seconds.count() << " seconds" << std::endl;
24
25   // std::chrono::minutes minutes(aSecond);
26   std::chrono::minutes minutes(
     std::chrono::duration_cast<
     std::chrono::minutes>(aSecond));
27   std::cout << minutes.count()
               << " minutes(truncated value)" << std::endl;
28
29   //std::chrono::hours hours(aSecond);
30   std::chrono::hours hours(
     std::chrono::duration_cast<
     std::chrono::hours>(aSecond));
31   std::cout << hours.count() << " hours( truncated value)"
               << std::endl;
32
33   std::cout << std::endl;
34
35   typedef std::chrono::duration<double, std::ratio<60>>
       MyMinuteTick;
36   MyMinuteTick myMinute(aSecond);
37   std::cout << myMinute.count() << " minutes" << std::endl;
38
39   typedef std::chrono::duration<double, std::ratio<3600>>
       MyHourTick;
40   MyHourTick myHour(aSecond);
41   std::cout << myHour.count() << " hours" << std::endl;
42
43   typedef std::chrono::duration<double, std::ratio<2700>>
       MyLessonTick;
44   MyLessonTick myLesson(aSecond);
45   std::cout << myLesson.count() << " lessons" << std::endl;
46
47   typedef std::chrono::duration<long long, std::ratio<1,2>>
       MyHalfASecondTick;
48   MyHalfASecondTick myHalfASecond(aSecond);
49   std::cout << myHalfASecond.count() << " HalfASeconds"
               << std::endl;
50
51   std::cout << std::endl;
52
53 }
Listing 19.38 Eine Sekunde, in verschiedenen Zeiteinheiten ausgegeben

In Listing 19.38 ist das Programm duration.cpp in Aktion zu sehen. aSecond(1) vom Typ MySecondTick in Zeile 11 ist die Zeitdauer, die es auszugeben gilt. Es ist in Abbildung 19.22 schön zu sehen, dass die Werte für die Minute und die Stunde abgeschnitten werden. Das wird durch die explizite Konvertierung std::chrono::duration_cast in den Zeilen 25 und 30 erreicht. Ein naiver Aufruf von std::chrono::minutes minutes(aSecond) führt zum Kompilierungsfehler, da sich eine Sekunde nicht als natürliche Zahl mit der Einheit Minute darstellen lässt. Für die exakte Darstellung der Sekunde in einer Minute und einer Stunde werden in den Zeilen 35 und 49 eigene Datentypen MyMinuteTick und MyHourTick definiert. Aber auch die eigenen Datentypen MyLessonTick oder MyHalfASecondTick sind schnell definiert, um eigene Zeiteinheiten zu verwenden.

Die Zeitgeber in C++11 umfassen eine Zeitdauer mit einem Zeitpunkt, sodass der aktuelle Zeitpunkt durch now() zurückgegeben werden kann. Im neuen C++11-Standard werden drei verschiedene Zeitgeber angeboten.

Jeder dieser Zeitgeber zeichnet sich durch ein einheitliches Interface aus.

Lediglich für den Zeitgeber std::chrono::steady_clock gilt, dass is_steady == true ist, denn dessen Werte können nie kleiner werden. Durch dieses einheitliche Interface lässt sich gegebenenfalls schnell das Programm von std::chrono::high_resolution_order auf std::chrono::system_clock modifizieren, da insbesondere std::chrono::high_resolution_order deutlich teurer in der Anwendung ist.

Mithilfe der Typsynonyme aus Listing 19.39 und dem neuen Schlüsselwort auto lässt es sich einfach bestimmen, wie viele Nanosekunden seit der Epoche (1.1.1970) vergangen sind. Nanosekunden sind die Genauigkeit der Systemzeit auf meiner Plattform.

clock.cpp

In Listing 19.40 ist schön zu sehen, wie die statische Funktion system_clock()::now() in Zeile 8 verwendet wird, um den aktuellen Zeitpunkt zu erhalten. Dieser ist der Schlüssel für die Zeitdauer duration seit der Epoche in Zeile 9. Eine weitere statische Funktion von std::chrono::system_clock wird in Zeile 13 angewandt, um die Frage zu beantworten, ob diese stetig ist. Das bisschen Ausgabe ist in Abbildung 19.23 zu sehen.

system_clock

Als einziger Zeitgeber besitzt std::chrono::system_clock zwei Methoden, um mit der C-API zu interagieren.

Auf POSIX-Systemen wird std::chrono::system_clock auf times, auf Windows-Systemen auf GetProcessTimes abgebildet.

<utility>

Ein std::reference_wrapper<T> ist ein kopierkonstruierbarer und zuweisbarer Wrapper um ein Objekt vom Typ T&. Damit entsteht ein Objekt, das sich zwar wie eine Referenz verhält, aber, und das ist der entscheidende Punkt, auch kopiert werden kann.

Diese Eigenschaften ermöglichen zwei neue Anwendungsfälle von std::reference_wrapper<T> gegenüber Referenzen.

Während die Verwendung von Referenz-Wrappern in Containern aus dem Kapitel 5, Abschnitt „Referenz-Wrapper“ der Teil I bekannt ist, fehlt noch der zweite Anwendungsfall.

referenceWrapperClass.cpp

Werden Instanzen der Klasse Bad (Zeile 32) in Listing 19.43 zugewiesen, moniert dies der GCC-Compiler unmissverständlich mit der eindeutigen Fehlermeldung aus Abbildung 19.24.

Dank Referenz-Wrapper wird dieser Anwendungsfall mit der Klasse Good unterstützt. Die Zuweisung in Zeile 48 führt zur Modifikation von g2 (Abbildung 19.25).

Der wesentliche Unterschied der Klasse Good (Zeile 12) gegenüber Bad (Zeile 5) ist, dass Good den std::string in einem Referenz-Wrapper hält (Zeile 19). Interessant ist auch die Elementfunktion changeMessage in Zeile 18. Durch die get-Methode des Referenz-Wrappers steht eine Referenz auf das interne Datenobjekt zur Verfügung, sodass dieses (Zeile 19) auf einen neuen Wert gesetzt werden kann. Somit werden g1 und g2 in Abbildung 19.25 modifiziert.

Interface

Das Interface der Referenz-Wrapper ist schnell erklärt. Neben der bereits verwendeten get-Methode, die eine Referenz auf das interne Objekt anbietet, ist der Klammeroperator für Referenz-Wrapper überladen. Damit lässt sich eine aufrufbare Einheit in einem Referenz-Wrapper kapseln und anwenden.

Für die Funktion foo aus Listing 19.44 wird in Zeile 1 ein Funktionstyp deklariert. Dieser wird in Zeile 2 verwendet, um einen Referenz-Wrapper zu definieren. Zuletzt wird der Referenz-Wrapper refWrap1() angewandt und die Funktion foo ausgeführt.

Die Hilfsfunktionen ref und cref

Für das einfache Definieren einer Referenz oder einer Referenz auf ein konstantes Objekt bietet C++11 die zwei Funktionen std::ref und std::cref an. Beide Funktionen nehmen ein Argument an und verpacken es. In Listing 19.45 werden die beiden Hilfsfunktionen angewandt.

refCref.cpp

Die Funktion invokeMe steht in zwei Varianten in Listing 19.45 zur Verfügung. In Zeile 5 nimmt sie eine Referenz auf einen std::string, in Zeile 9 eine Referenz auf einen konstanten std::string an. Durch den Aufruf invokeMe(std::ref(s)) wird die nicht konstante, durch den Aufruf invokeMe(std::cref(s)) die konstante Version verwendet. Das Funktions-Template in Zeile 14 verdoppelt ihr Argument. Dazu ist es aber notwendig, dass der Funktionskörper auf einer Referenz agiert. Genau dies wird mit den Funktionsaufrufen in Zeile 36 doubleMe(std::ref(i)) und Zeile 41 doubleMe(std::ref(a)) erreicht. In der Ausgabe in Abbildung 19.26 ist schön zu sehen, dass der Funktionsaufruf doubleMe(i) in Zeile 33 i nicht verdoppelt.

invokeInThread.cpp

Aufgabe 19-16

Führen Sie ein paar Funktionen in einem separaten Thread aus.

Die fünf Funktionen geben jeweils einen einfachen String auf der Konsole aus.

void func1(){std::cout << "Only ";}
void func2(){std::cout << "for ";}
void func3(){std::cout << "testing ";}
void func4(){std::cout << "purpose";}
void func5(){std::cout << ".\n";}

Verpacken Sie diese Funktion in einen Referenz-Wrapper, schieben Sie sie auf einen Vektor und führen Sie die Funktionen in dem Vektor in einem separaten Thread aus. Diese Aufgabe lässt sich auch mit dem neuen C++11-Feature std::function lösen, indem die Funktionen in Funktionsobjekte verpackt werden.