Kapitel 20. Verbesserte Bibliotheken

In diesem Kapitel:

Die verbesserten Bibliotheken in C++11 basieren auf bestehenden C++-Bibliotheken und runden diese ab. Das prominenteste Beispiel ist die neue Bibliothek zu Smart Pointern, die dem klassischen Smart Pointer std::auto ptr gleich drei neue Smart Pointer zur Seite stellt. Aber auch die Container std::tuple und std::array, die Hashtabellen, neue Algorithmen und die zwei funktionalen Bausteine std::bind und std::function machen C++11 zu einer moderneren und somit besseren Programmiersprache.

Smart Pointer

Die Anforderungen an Smart Pointer sind vielfältig. Daher verwundert es nicht, das C++11 drei neue Exemplare anbietet. So verfolgt der Smart Pointer std::shared_ptr das Konzept des geteilten Besitzverhältnisses und der std::unique_pt das des exklusiven Besitzverhältnisses. Für Zyklen von Smart Pointern hingegen ist der std::weak_ptr verantwortlich.

<memory>

unique_ptr

std::unique_ptr ersetzt std::auto_ptr, der in C++11 deprecated ist. Beides sind Smart Pointer, die exklusiv eine Ressource besitzen und den transparenten Zugriff auf diese erlauben. Beide bieten ein sehr ähnliches Interface an. Bevor es in die Details geht, stellt Listing 20.1 das Interface von std::unique_ptr vor.

uniquePtr.cpp

01 #include <iomanip>
02 #include <iostream>
03 #include <memory>
04 #include <utility>
05
06 struct MyStruct{
07   MyStruct(int v):val(v){
08     std::cout << std::setw(10) << std::left
                 << (void*) this << " Hello: " << val
         << std::endl;
09   }
10   ~MyStruct(){
11     std::cout << std::setw(10) << std::left
          << (void*)this << " Good Bye: " << val
          << std::endl;
12   }
13   int val;
14 };
15
16 int main(){
17
18   std::cout << std::endl;
19
20   { // begin of scope
21
22   // Initialize with resource
23   std::unique_ptr<MyStruct> uniquePtr0{new MyStruct(0)};
24
25   // use an std::auto_ptr
26   std::auto_ptr<MyStruct> autoPtr{new MyStruct(1)};
27   std::unique_ptr<MyStruct> uniquePtr1{std::move(autoPtr)};
28
29   // Default Constructor
30   std::unique_ptr<MyStruct> uniquePtr2;
31
32   // Move Constructor
33   std::unique_ptr<MyStruct> uniquePtr4{new MyStruct(2)};
34   std::unique_ptr<MyStruct> uniquePtr5{std::move(uniquePtr4)};
35
36   // Move Assignment
37   std::unique_ptr<MyStruct> uniquePtr6{new MyStruct(3)};
38   std::unique_ptr<MyStruct> uniquePtr7= std::move(uniquePtr6);
39
40   // access the resource
41   std::cout << std::endl;
42   std::cout << "Address of resource of uniquePtr7"
        << (void*)uniquePtr7.get() << " " << std::endl;
43   std::cout << "Get val: uniquePtr7.get()->val: "
        << uniquePtr7.get()->val << std::endl;
44   std::cout << "Get val: uniquePtr7->val: "
       <<  uniquePtr7->val << std::endl;
45   std::cout << std::endl;
46
47   // release the resource
48   MyStruct* myStruct= uniquePtr7.release();
49   std::cout << "myStruct->val: " << myStruct->val
        << std::endl;
50   delete myStruct;
51
52   std::cout << std::endl;
53
54   // reset the resource
55   uniquePtr2.reset(new MyStruct(4));
56   std::unique_ptr<MyStruct> uniquePtr8{new MyStruct(5)};
57   uniquePtr8.reset(new MyStruct(6));
58
59   std::cout << std::endl;
60
61   // swap the std::unique_ptr
62   uniquePtr2.swap(uniquePtr1);
63   std::swap(uniquePtr2,uniquePtr1);
64
65   } // end of scope
66
67   std::cout << std::endl;
68
69 }
Listing 20.1 Das Interface von std::unique_ptr

In Listing 20.1 hat MyStruct (Zeile 6) die Aufgabe, den Wert der Instanzvariablen val, ihre Adresse und eine kurze Nachricht im Konstruktor und Destruktor auszugeben. Damit ist es leichter, die Lebenszeit der Objekte vom Typ MyStruct in Abbildung 20.1 zu verfolgen. Ein std::unique_ptr bietet verschiedene Varianten der Instanziierung an. Er kann über einen Zeiger auf eine Ressource (Zeile 23), einen std::auto_ptr (Zeile 27) oder auch den Aufruf des Standardkonstruktors (Zeile 30) instanziiert werden. Die Initialisierung über ein Rvalue (Zeilen 34 und 38) wird unterstützt, für einen Lvalue wird sie unterbunden. Ist der std::unique_ptr uniquePtr7 initialisiert, lässt sich durch uniquePtr7.get() (Zeilen 42 und 43) auf die Ressource und durch uniquePtr7-> (Zeile 44) auf die Elemente der Ressource zugreifen. Um die Ressource freizugeben, steht uniquePtr7.release() (Zeile 48) bereit. Durch das explizite Löschen der Ressource in Zeile 50 wird ein Speicherloch vermieden. Eine neue Ressource kann mit uniquePtr2.reset(new MyStruct(4)) (Zeile 55) gesetzt werden. Besitzt der std::unique_ptr bereits eine Ressource, wird die ursprüngliche Ressource gelöscht (Zeile 56). Dies lässt sich auch direkt in Abbildung 20.1 nachvollziehen. Über uniquePtr2.swap(uniquePtr1) oder auch std::swap(uniquePtr2,uniquePtr1) lassen sich zwei std::unique_ptr tauschen.

auto_ptr versus unique_ptr

Der feine, aber entscheidende Unterschied zwischen std::unique_ptr und std::auto_ptr ist, dass beim Kopieren eines std::auto_ptr dessen Ressource verschoben wird. Was oberflächlich wie Copy-Semantik wirkt, ist unter der Decke Move-Semantik. In Kapitel 5 im Abschnitt „Smart Pointer“ wird das implizite Verschieben einer Ressource mit std::auto_ptr und das explizite Verschieben mit std::unique_ptr bildlich gegenübergestellt.

Wie wird nun verhindert, dass std::unique_ptr kopiert werden kann? Die Antwort gibt die Definition von std::unique_ptr in Listing 20.2.

In der Implementierung von std::unique_ptr (GCC 4.7, 2011) wird das C++11-Schlüsselwort delete angewandt, um die Copy-Semantik zu unterbinden. Sowohl den Move-Konstruktor unique_ptr(const unique_ptr&& __u) als auch den Move-Zuweisungsoperator unique_ptr& operator=(unique_ptr&& __u) bietet dieser Smart Pointer an. Soll ein std::unique_ptr kopiert werden, muss der Umweg über std::move gegangen werden.

Sink and Source-Idiom

Das Sink and Source-Idiom beschreibt zwei Funktionen, die eine Ressource verwalten. Dabei ist die Quelle (source) die Funktion, die die Ressource bereitstellt und deren Besitz an die Funktion Senke (sink) auf Anfrage übergibt. Das definierte Zusammenspiel, und das ohne potenzielle Speicherlöcher, lässt sich elegant mit std::unique_ptr in Listing 20.4 formulieren.

sinkSource.cpp

Listing 20.4 dient nur der Illustration des Grundproblems, das mit dem Sink and Source-Idiom gelöst wird: Eine Funktion stellt die Ressource bereit und übergibt den Besitz einer anderen Funktion. Wer ist nun für das Freigeben der Ressource zuständig? Die kritische Ressource ist in diesem konkreten Fall BigData, das ein beliebig großes, dynamisch allokiertes Array myData besitzt. Betrachten wir zuerst den typischen Anwendungsfall in Zeile 37. Wird source(100000000) prozessiert, allokiert die Funktion source (Zeile 15) BigData und stellt es über seinen Rückgabewert zur Verfügung. Die Funktion sink in Zeile 21 verhält sich genau spiegelbildlich zur Funktion source, denn sie benötigt einen std::unique_ptr<BigData> als Eingabewert. Die Eleganz dieses Idioms ist, dass, wie auch immer die Funktionen sink und source verwendet werden, die Ressource automatisch freigegeben wird.

  • source(123456789):

    Der Rückgabewert verliert am Ende des Funktionskörpers von source seine Gültigkeit und wird automatisch gelöscht.

  • sink(source(100000000)):

    Der Rückgabewert von source ist ein Rvalue, sodass die Ressource zum Aufruf sink (Move-Semantik) verschoben wird. Am Ende des Funktionskörpers von sink verliert der std::unique_ptr seine Gültigkeit und wird automatisch gelöscht.

  • source(1000):

    Stellt eine Variation von source(12345679) dar.

  • sink( std::unique_ptr<BigData>( new BigData(2011))):

    Die Funktion sink fordert die Ressource direkt. Da diese Ressource ein Rvalue ist, wird sie verschoben.

Abbildung 20.2 zeigt den Programmlauf von Listing 20.4.

auto_ptr versus unique_ptr

Der explizite Übergang der Besitzverhältnisse beim std::unique_ptr gegenüber dem impliziten beim deprecated std::auto_ptr ist aber noch nicht das Ende der Geschichte. std::unique_ptr zeichnet sich in weiteren Punkten gegenüber std::auto_ptr aus. Er kann Arrays verwalten, über eine Löschfunktion parametrisiert und in der Standard Template Library verwendet werden. Zuerst die zusätzlichen Features von std::unique_ptr der Reihe nach.

Zum Abschluss zeigt Listing 20.5 den Umgang mit einem Array.

uniquePtrArray.cpp

Die Datenstruktur MyStruct (Zeile 5) in Listing 20.5 zählt über die statische Variable MyStruct::count mit, wie viele Instanzen der Struktur existieren. Dazu wird sie im Konstruktor in- und im De struktor dekrementiert. Neben ihrem Wert wird in den beiden Funktionen zusätzlich die Adresse der Instanz ausgegeben. Die einfache Verwendung eines std::unique_ptr<MyStruct[]> zeigt Zeile 29. Um den Lebenszyklus der Ressource MyStruct leichter zu verfolgen, wurde die Gültigkeit von std::unique_ptr<MyStruct[]> auf den Bereich (Zeilen 27 bis 31) eingeschränkt. In Abbildung 20.3 ist schön zu sehen, wie die Instanzen vom Typ MyStruct am Ende ihres Gültigkeitsbereichs (Zeile 31) in umgekehrter Reihenfolge ihrer Erzeugung automatisch gelöscht werden. In Zeile 41 wird dem ersten myUniqueArray-Element eine neue MyStruct-Instanz zugewiesen. Aber auch einer neuen MyStruct-Instanz kann ein myUniqueArray-Element in Zeile 53 zugewiesen werden.

vectorUniquePtr.cpp

Aufgabe 20-1

Verwenden Sie einen std::unique_ptr in einem std::vector.

Instanziieren Sie einen Vektor vom Typ std::vector<std::unique_ptr<int>>. Geben Sie seine Elemente ab- und aufsteigend sortiert aus.

std::shared_ptr stellt den typischen Anwendungsfall für Smart Pointer in C++11 dar. Mit ihm lässt sich eine Ressource gemeinsam nutzen. Jeder std::shared_ptr hält einen Referenzzähler auf einen Zähler und auf eine gemeinsam genutzte Ressource. Wird nun der std::shared_ptr kopiert, wird der Referenzzähler erhöht. Beim Löschen des std::shared_ptr wird dieser dekrementiert. Erreicht der Referenzzähler den Wert 0, führt dies zur automatischen Löschung der Ressource. Der std::shared_ptr bietet ein ähnliches Interface wie der std::unique_ptr und der aus C++98 bekannte std::auto_ptr an. sharedPtr.cpp in Listing 20.6 sollte nach uniquePtr.cpp in Listing 20.1 vertraut wirken.

sharedPtr.cpp

01 #include <iomanip>
02 #include <iostream>
03 #include <memory>
04 #include <utility>
05
06 struct MyStruct{
07   MyStruct(int v):val(v){
08     std::cout << std::setw(10) << std::left
                 << (void*) this << " Hello: " << val
                 << std::endl;
09   }
10   ~MyStruct(){
11     std::cout << std::setw(10) << std::left
                 << (void*)this << " Good Bye: " << val << std::endl;
12   }
13   int val;
14 };
15
16 int main(){
17
18   std::cout << std::endl;
19
20   { // begin of scope
21
22   // Initialize with resource
23   std::shared_ptr<MyStruct> sharedPtr{new MyStruct(0)};
24
25   // use an std::auto_ptr
26   std::auto_ptr<MyStruct> autoPtr{new MyStruct(1)};
27   std::shared_ptr<MyStruct> sharedPtr1{std::move(autoPtr)};
28
29   std::unique_ptr<MyStruct> uniquePtr{new MyStruct(2)};
30   std::shared_ptr<MyStruct> sharedPtr2{std::move(uniquePtr)};
31
32   // Default Constructor
33   std::shared_ptr<MyStruct> sharedPtr3;
34
35   // Move Constructor
36   std::unique_ptr<MyStruct> uniquePtr1{new MyStruct(3)};
37   std::shared_ptr<MyStruct> sharedPtr4{std::move(uniquePtr1)};
38
39   // Move Assignment
40   std::unique_ptr<MyStruct> uniquePtr2{new MyStruct(4)};
41   std::shared_ptr<MyStruct> sharedPtr5= std::move(uniquePtr2);
42
43   // test, if unique owner of the resource
44   std::cout << std::boolalpha << std::endl;
45   std::cout << "sharedPtr5.unique(): "
               << sharedPtr5.unique() << std::endl;
46   std::cout << std::endl;
47
48   // Copy Constructor form a std::shared_ptr
49   std::shared_ptr<MyStruct> sharedPtr6{new MyStruct(5)};
50   std::shared_ptr<MyStruct> sharedPtr7{sharedPtr6};
51
52   // Copy Assignment from a std::shared_ptr
53   std::shared_ptr<MyStruct> sharedPtr8= sharedPtr6;
54
55   // get the reference count
56   std::cout << std::endl;
57   std::cout << "sharedPtr8.use_count(): "
               << sharedPtr8.use_count() << std::endl;
58
59   // access the resource
60   std::cout << std::endl;
61   std::cout << "Address of resource of sharedPtr8"
               << (void*)sharedPtr8.get() << " " << std::endl;
62   std::cout << "Get val: sharedPtr8.get()->val: "
               << sharedPtr8.get()->val << std::endl;
63   std::cout << "Get val: sharedPtr8->val: "
               << sharedPtr8->val << std::endl;
64   std::cout << std::endl;
65
66   // reset the resource
67   sharedPtr8.reset(new MyStruct(8));
68
69   std::cout << std::endl;
70
71   // only sharedPtr8 will be reset
72   std::cout << "sharedPtr6.use_count(): "
               << sharedPtr6.use_count() << std::endl;
73   std::cout << "sharedPtr6.get->val: "
               << sharedPtr6.get()->val << std::endl;
74   std::cout << "sharedPtr7.use_count(): "
               << sharedPtr7.use_count() << std::endl;
75   std::cout << "sharedPtr7.get->val:"
               << sharedPtr7.get()->val << std::endl;
76
77   std::cout << std::endl;
78   std::cout << "sharedPtr8.use_count(): "
               << sharedPtr8.use_count() << std::endl;
79   {
80     std::shared_ptr<MyStruct> sharedPtr9{sharedPtr8};
81     std::shared_ptr<MyStruct> sharedPtr10= sharedPtr9;
82     std::cout << "sharedPtr8.use_count(): "
                  << sharedPtr8.use_count() << std::endl;
83     sharedPtr10.reset();
84     std::cout << "sharedPtr8.use_count(): "
                 << sharedPtr8.use_count() << std::endl;
85   }
86   std::cout << "sharedPtr8.use_count(): "
               << sharedPtr8.use_count() << std::endl;
87   sharedPtr8.reset();
88
89   std::cout << std::endl;
90
91   // swap the std::shared_ptr
92   sharedPtr2.swap(sharedPtr1);
93   std::swap(sharedPtr2,sharedPtr1);
94
95   } // end of scope
96
97   std::cout << std::endl;
98
99 }
Listing 20.6 Das Interface von std::shared_ptr

Die einfache Datenstruktur MyStruct aus Listing 20.1 findet in Listing 20.6 ihre Wiederverwendung. Zuerst wird std::shared_ptr in verschiedenen Varianten instanziiert. Das findet in Zeile 23 durch eine Ressource, in Zeile 25 durch einen std::auto_ptr und in Zeile 40 durch ein std::unique_ptr statt. Entsprechend dem std::unique_ptr besitzt der std::shared_ptr auch einen Standardkonstruktor (Zeile 33). Ein std::shared_ptr kann über einen std::unique_ptr initialisiert werden (Zeilen 36 und 40). Mit der Elementfunktion unique lässt sich in Zeile 45 testen, ob der std::shared_ptr als einziger eine Ressource besitzt. Durch den Aufruf des Kopierkonstruktors in Zeile 50 und den Kopierzuweisungsoperator in Zeile 53 besitzt der Referenzzähler sharedPtr8.use_count() in Zeile 57 den Wert 3. std::shared_ptr ist eng verwandt mit std::unique_ptr. Dies ist einfach an den Aufrufen in Zeile 61 bis 63 zu sehen, denn durch sharedPtr8.get() kann auf die Ressource, durch sharedPtr8->val auf den Wert von MyStruct zugegriffen werden.

Erhält die Elementfunktion reset beim Aufruf eine Ressource (Zeile 68), bewirkt dies, dass sharedPtr8 der alleinige Besitzer der neuen Ressource ist. sharedPtr6 und sharedPtr7 teilen sich MyStruct5 aus Zeile 44. Ihr Referenzzähler besitzt jetzt den Wert 2. In dem Bereich zwischen Zeile 80 und Zeile 85 wird der Referenzzähler von sharedPtr8 zu Beginn wieder auf 3 erhöht. sharedPtr10.reset() ohne Argument bewirkt, dass sharedPtr10 zurückgesetzt wird. Der Referenzzähler von sharedPtr8 wird dekrementiert. Dekrementiert wird er nochmals beim Verlassen des Bereichs, denn sharedPtr9 verliert seine Gültigkeit. Ein letztes Mal sharedPtr8.reset() in Zeile 87, und die Ressource MyStruct(8) kann zerstört werden. Zum Abschluss folgt noch ein unspektakuläres Tauschen der std::shared_ptr. Diese lange Ausführung in Prosa lässt sich natürlich auch auf der Konsole in Abbildung 20.4 bewundern.

STL-konform

Zum anderen wird dadurch das falsche wiederholte Löschen einer Ressource verhindert. Listing 20.8 zeigt die falsche Benutzung von std::shared_ptr. Um den Blick aufs Wesentliche zu richten, habe ich auf die Ausnahmebehandlung verzichtet.

sharedPtrDouDelete.cpp

Das Ausführen des Programms bringt es in Abbildung 20.5 auf den Punkt.

std::shared_ptr sowie std::unique_ptr lassen sich über sogenannte Löschfunktionen parametrisieren. In diesem Fall wird zum Löschen der Ressource die benutzerdefinierte Löschfunktion verwendet. Dabei sind die Forderungen an die Löschfunktion d, dass sie eine aufrufbare Einheit ist. Statt delete p für die Ressource p wird in diesem Fall d(p) aufgerufen, wenn die Ressource p destruiert wird. Listing 20.9 zeigt die Anwendung einer Löschfunktion, die darüber hinaus über die statische Variable count mitzählt, wie oft sie schon aufgerufen wurde.

sharedPtrDeleter.cpp

01 #include <iostream>
02 #include <memory>
03 #include <random>
04 #include <typeinfo>
05 #include <utility>
06
07 template <typename T>
08 class Deleter{
09 public:
10   void operator()(T *ptr){
11     ++Deleter::count;
12     // do the actual work
13     delete ptr;
14   }
15   void getInfo(){
16     std::string typeId{typeid(T).name()};
17     size_t sz= Deleter::count * sizeof(T);
18     std::cout << "Deleted " << Deleter::count
                 << " objects of type: " << typeId
                 << std::endl;
19     std::cout << "Freed size in bytes: "  << sz << "."
                 <<  std::endl;
20     std::cout << std::endl;
21
22   }
23 private:
24   static int count;
25 };
26
27 template <typename T>
28 int Deleter<T>::count=0;
29
30 typedef Deleter<int> IntDeleter;
31 typedef Deleter<double> DoubleDeleter;
32
33 void createRandomNumbers(){
34
35   std::random_device seed;
36
37   // generator
38   std::mt19937 engine(seed());
39
40   // distribution
41   std::uniform_int_distribution<int> thousand(1,1000);
42   int ranNumber= thousand(engine);
43   for ( int i=0 ; i <= ranNumber; ++i)
           std::shared_ptr<int>(new int(i),IntDeleter());
44
45 }
46
47 int main(){
48
49   std::cout << std::endl;
50
51   // declare a local scope
52   {
53     std::shared_ptr<int> sharedPtr1( new int,IntDeleter() );
54     std::shared_ptr<int> sharedPtr2( new int,IntDeleter() );
55     auto intDeleter=
            std::get_deleter<IntDeleter>(sharedPtr1);
56     intDeleter->getInfo();
57     sharedPtr2.reset();
58     intDeleter->getInfo();
59
60   }
61   // create up to 1000 std::shared_ptr of type int
62   createRandomNumbers();
63
64   // declare a local scope
65   {
66
67     // create three Smart pointer for doubles
68     std::unique_ptr<double,DoubleDeleter >
            uniquePtr( new double, DoubleDeleter() );
69     std::unique_ptr<double,DoubleDeleter >
            uniquePtr1( new double, DoubleDeleter() );
70     std::shared_ptr<double> sharedPtr( new double, DoubleDeleter() );
71
72     std::shared_ptr<double> sharedPtr4(std::move(uniquePtr));
73     std::shared_ptr<double> sharedPtr5= std::move(uniquePtr1);
74
75   }
76
77   IntDeleter().getInfo();
78   DoubleDeleter().getInfo();
79
80 }
Listing 20.9 std::shared_ptr und std::unique_ptr, mit einer Löschfunktion parametrisiert

Deleter (Zeile 7) in Listing 20.9 ist eine aufrufbare Einheit. Da sie kopierkonstruierbar ist, lässt sie sich als Löschfunktion anwenden. Tatsächlich ist es ein Klassen-Template, das seine statische Variable count (Zeile 24) im Aufrufoperator (Zeile 10) inkrementiert. Die gesammelte Information stellt das Template über die Funktion getInfo (Zeile 15) zur Verfügung. Die zwei Typsynonyme in Zeile 30 und 31 ersparen ein bisschen Tipparbeit.

Bevor ich das Hauptprogramm beschreibe, noch ein paar Worte zur Funktion createRandomNumbers. Mithilfe der neuen C++11-Zufallszahlen-Bibliothek (siehe Kapitel 19, Abschnitt „Zufallszahlen“) werden bis zu 1.000 verschiedene Zufallszahlen erzeugt und genauso viele std::shared_ptr in Zeile 43 instanziiert. In dem ersten lokalen Bereich in den Zeilen 52 bis 60 werden zwei std::shared_ptr erzeugt. Über den Aufruf std::get_deleter<IntDeleter>(sharedPtr1) steht die Löschfunktion von sharedPtr2 zur Verfügung. Dieser kann benutzt werden, um Information über die Anzahl der Löschaufrufe zu erhalten. Erst nach dem Aufruf von sharedPtr2.reset() wurde ein int-Datentyp gelöscht. Das ist in Abbildung 20.6 schön zu sehen. Genauso gut ist zu sehen, dass die Anzahl der Instanzen vom Typ std::shared_ptr<int> deutlich nach dem Aufruf von createRandom Numbers in Zeile 62 steigt. Deleter ist ein Klassen-Template, sodass sich Smart Pointer von double-Datentypen mit ihm parametrisieren lassen. In den Zeilen 68 und 69 wird dazu std::unique_ptr erzeugt. Durch den Move-Konstruktoraufruf in Zeile 72 und den Move-Zuweisungsaufruf in Zeile 73 werden die std::shared_ptr<double> die neuen Besitzer der Ressource und ihrer Löschfunktion. Dass die Löschfunktion ordentlich Buch führt, zeigt Abbildung 20.6.

enable_shared_from_this und shared_from_this

Die Funktion std::shared_from_this ist eine praktische Hilfsfunktion, um aus einem bestehenden Objekt einen std::shared_ptr auf dieses zurückzugeben. Dazu ist es lediglich notwendig, dass die Klasse von std::enable_shared_from_this abgeleitet ist.

Die Funktion std::shared_from_this ist einfacher mit dem Listing 20.10 erklärt, als mit vielen Worten beschrieben.

enabledShared.cpp

Was in Listing 20.10 wie Magie wirkt, hat in C++ einen Namen: Curiously Recurring Template Pattern (CRTP). SharedMe in Zeile 4 ist solch ein Exemplar. Das Besondere daran ist, dass die abgeleitete Klasse SharedMe Template-Argument der Basisklasse std::enable_shared_from_this ist. Die Details zu diesem bekannten C++-Idiom lassen sich in dem Buch »C++ Templates« von David Vandevoorde und Nicolai Josuttis nachlesen (Vandevoorde & Josuttis, 2002). Das Entscheidende an SharedMe ist, dass die Elementfunktion getShared einen Smart Pointer vom Typ std::shared_ptr<SharedMe> in Zeile 7 zurückgibt. Dazu verwendet die Funktion den Aufruf shared_from_this. Das Hauptprogramm ist unspektakulär. Durch shareMe->getShared() in Zeile 17 wird eine Referenz auf ShareMe zurückgegeben. Dies ist einfach zu sehen. Denn einerseits besitzen die Ressourcen beider Smart Pointer die gleiche Adresse (Zeilen 20 und 21), und andererseits beträgt der shareMe.use_count()-Zähler 2. Beides lässt sich direkt von der Ausgabe in Abbildung 20.7 ablesen.

std::shared_ptr, std::unique_ptr und der im nächsten Abschnitt vorgestellte std::weak_ptr unterstützen die implizite Konvertierung in Ableitungshierarchien. Nur der deprecated std::auto_ptr macht die Ausnahme. So ist ein Konstruktoraufruf std::shared_ptr<Base> sp(new Der) zulässig, falls Der von Base öffentlich abgeleitet ist. Dies trifft nicht nur auf den Konstruktor, den Kopierkonstruktor und den Kopierzuweisungsoperator zu, sondern im Fall von std::shared_ptr auch auf die reset-Elementfunktion. Die gleiche Argumentation für die implizite Konvertierung einer Ressource lässt sich auch direkt auf die Smart Pointer selbst anwenden. Ob die implizite Konvertierung eines Smart Pointer std::shared_ptr<Base> sp(sp1) (sp1 ist vom Typ std::shared_ptr<Der>) zulässig ist, hängt davon ab, ob die Ressource Der von Base öffentlich abgeleitet ist.

dynamic_pointer_cast, static_pointer_cast und const_pointer_cast

std::dynamic_pointer_cast, std::static_pointer_cast und std::const_pointer_cast verhalten sich so wie ihre bekannten Namensverwandten std::dynamic_cast, std::static_cast und std::const_cast aus C++. Dies lässt sich leicht einsehen, denn ein std::dynamic_pointer_cast<Der>(p) setzt voraus, dass dynamic_cast<Der*>(p.get()) gültig ist. Semantisch ist std::dynamic_pointer_cast<Der>(p) äquivalent zu std::shared_ptr<Der>(std::dynamic_cast<Der*>(p.get())). Dies gilt natürlich auch für die Varianten std::static_pointer_cast und std::const_pointer_cast. std::reinterpret_pointer_cast habe ich nicht vergessen. Diese Variante existiert nicht in C++11.

Aufgabe 20-2

Hinterfragen Sie die Verwendung von std::auto_ptr in Ihrem Sourcecode.

std::auto_ptr ist deprecated in C++11. Dass std::auto_ptr deprecated ist und insbesondere die Tatsache, dass std::auto_ptr heimlich die Ressource verschiebt, ist Grund genug, den Sourcecode auf dessen Einsatz zu hinterfragen. Entscheiden Sie daher im Einzelfall, ob std::unique_ptr oder std::shared_ptr der Ersatz für std::auto_ptr ist. Die Umstellung sollte kurz und schmerzlos sein, denn die neuen std::unique_ptr und std::shared_ptr bieten ein sehr ähnliches Interface wie std::auto_ptr an. Lediglich das implizite Verschieben der Ressource ist mit std::unique_ptr nicht möglich und muss mit std::move explizit angefordert werden.

Aufgabe 20-3

Automatisches Speichermanagement mit Ganzzahlen

Variieren Sie das kleine Programm in Listing 20.11. Verwenden Sie einen std::unique_ptr und einen std::shared_ptr für das automatische Verwalten der Ressource und vergleichen Sie die Ausführungszeiten.

Mit dem GCC 4.7-Compiler und dem Optimierungsflag -03 übersetzt, erhalte ich die folgenden Zeitangaben.

im Vergleich mit std::unique_ptr und std::shared_ptr
Abbildung 20.8 Listing 20.11 im Vergleich mit std::unique_ptr und std::shared_ptr

Damit ist std::unique_ptr genauso schnell wie das direkte Löschen der Ressource. std::shared_ptr benötigt ca. doppelt so lang.

nativeDuration.cpp

uniqueDuration.cpp

sharedDuration.cpp

Aufgabe 20-4

Verifizieren Sie die Aussagen zur Konvertierung in Ableitungshierarchien.

Leiten Sie eine Klasse public, protected und private von einer Klasse Base ab. Initialisieren Sie einen Smart Pointer std::shared_ptr<Base>, indem Sie die drei abgeleiteten Klassen von Base verwenden.

Entspricht das Ergebnis Ihren Erwartungen?

Aufgabe 20-5

Eine mögliche Implementierung von std::enable_shared_from_this.

Wem die Erläuterung zu std::enable_shared_from_this im Abschnitt „shared_ptr von this“ nicht ausreicht, der sei auf eine mögliche Implementierung aus dem Standardentwurf N3242 (Becker, Working Draft, Standard for Programming Language C++ (N3242), 2011) von Pete Becker verwiesen.

template<class T>
class enable_shared_from_this {
  private:
  weak_ptr<T> __weak_this;
protected:
  constexpr enable_shared_from_this() : __weak_this() { }
  enable_shared_from_this(enable_shared_from_this const &) { }
  enable_shared_from_this& operator=(enable_shared_from_this
const &) { return *this; }
  ~enable_shared_from_this() { }
public:
  shared_ptr<T> shared_from_this() { return
                shared_ptr<T>(__weak_this); }
  shared_ptr<T const> shared_from_this() const { return
                shared_ptr<T const>(__weak_this); }
};

Der std::weak_ptr ist kein Smart Pointer im eigentlichen Sinn, denn er bietet keinen transparenten Zugriff auf seine Ressource. Er besitzt nicht einmal eine Ressource, er bekommt sie von einem std::shared_ptr geliehen. Da er nicht der Besitzer der Ressource ist, verändert er auch nicht den Referenzzähler auf diese. Die Existenzberechtigung für den std::weak_ptr ist es, zyklische Referenzen von std::shared_ptr aufzubrechen. Dazu reicht ihm ein einfaches Interface aus. Bevor zyklische Referenzen unser Thema sein werden, ein bewährter Blick auf die Schnittstelle von std::weak_ptr in Listing 20.12.

weakPtr.cpp

01 #include <iostream>
02 #include <memory>
03
04 class MyInt{
05 public:
06
07   MyInt(int i):i_(i){}
08   int get() const{ return i_; }
09
10 private:
11   int i_;
12 };
13
14 int main(){
15
16   std::cout << std::endl;
17
18   std::cout << std::boolalpha;
19
20   // default constructor
21   std::weak_ptr<MyInt> weakPtr;
22   std::cout << "weakPtr.use_count(): "
               << weakPtr.use_count() << std::endl;
23   std::cout << "weakPtr.expired(): " << weakPtr.expired()
               << std::endl;
24
25   std::cout << std::endl;
26
27   std::shared_ptr<MyInt> sharedPtr(new MyInt(2011));
28   std::cout << "sharedPtr.use_count(): "
               << sharedPtr.use_count() << std::endl;
29
30   // initialize weakPtr
31   weakPtr= sharedPtr;
32   std::cout << "weakPtr.use_count(): "
               << weakPtr.use_count() << std::endl;
33   std::cout << "weakPtr.expired(): " << weakPtr.expired()
               << std::endl;
34
35   std::weak_ptr<MyInt> weakPtr1(sharedPtr);
36
37   std::cout << std::endl;
38
39   // refer to the resource
40   std::cout << "sharedPtr->get(): " << sharedPtr->get()
               << std::endl;
41   // will not work with weakPtr
42   // std::cout << "weakPtr->get()" << weakPtr->get()
                  << std::endl;
43
44   if(std::shared_ptr<MyInt> sharedPtr1 = weakPtr.lock()) {
45     std::cout << "sharedPtr->get(): " << sharedPtr->get()
                 << std::endl;
46   }
47   else{
48     std::cout << "Don't get the resource!" << std::endl;
49   }
50
51   std::cout << std::endl;
52
53   // reset the weakPtr
54   weakPtr.reset();
55   if(std::shared_ptr<MyInt> sharedPtr1 = weakPtr.lock()) {
56       std::cout << "sharedPtr->get(): "
                   << sharedPtr->get() << std::endl;
57   }
58   else{
59     std::cout << "Don't get the resource!" << std::endl;
60   }
61
62   // swap weakPtr2 and weakPtr3
63
64   std::cout << std::endl;
65   std::shared_ptr<MyInt> sharedPtr2(new MyInt(2));
66   std::shared_ptr<MyInt> sharedPtr3(new MyInt(3));
67   std::weak_ptr<MyInt> weakPtr2(sharedPtr2);
68   std::weak_ptr<MyInt> weakPtr3(sharedPtr3);
69
70   if(std::shared_ptr<MyInt> sharedFromWeak2 =
        weakPtr2.lock()) {
71     std::cout << "sharedFromWeak2->get(): "
                 << sharedFromWeak2->get() << std::endl;
72   }
73
74   std::cout << std::endl;
75
76   weakPtr2.swap(weakPtr3);
77   if(std::shared_ptr<MyInt> sharedFromWeak2 =
        weakPtr2.lock()) {
78       std::cout << "sharedFromWeak2->get(): "
                   << sharedFromWeak2->get() << std::endl;
79   }
80
81   std::cout << std::endl;
82
83   std::swap(weakPtr2,weakPtr3);
84   if(std::shared_ptr<MyInt> sharedFromWeak2 =
        weakPtr2.lock()) {
85     std::cout << "sharedFromWeak2->get(): "
                 << sharedFromWeak2->get() << std::endl;
86   }
87
88   std::cout << std::endl;
89
90 }
Listing 20.12 Das Interface von std::weak_ptr

Die Klasse MyInt (Zeile 4) in Listing 20.12 ist lediglich eine dünne Hülle um den Datentyp int. Mit ihm lässt sich der Zugriff auf die Ressource einfach demonstrieren. std::weak_ptr besitzt einen Default-Konstruktor. Ohne eine Ressource besitzt weakPtr.use_count() in Zeile 22 den Wert 0 und weakPtr.expired() in Zeile 23 den Wert true. Das ändert sich, nachdem sharedPtr in Zeile 27 eine Ressource erhält, mit der weakPtr in Zeile 31 initialisiert wird. Ich will aber nochmals explizit darauf hinweisen, dass durch den Ausdruck weakPtr= sharedPtr in Zeile 31 der Referenzzähler nicht erhöht wird. Ein std::weak_ptr lässt sich auch direkt über einen std::shared_ptr (Zeile 35) initialisieren. Der std::weak_ptr bietet keine Schnittstelle für die direkte Adressierung der Ressource an. Dazu muss der Umweg über einen weakPtr.lock()-Aufruf und den resultierenden std::shared_ptr gegangen werden. In den Zeilen 44 bis 49 ist der idiomatische Weg dargestellt. Wird std::weak_ptr zurückgesetzt (Zeile 54), gibt weakPtr.lock() einen std::shared_ptr<MyInt>() zurück. Dieser evaluiert in dem logischen Ausdruck zu false, sodass der else-Zweig in Zeile 59 ausgeführt wird. Das bisher Ausgeführte und die Anwendung der swap-Funktion in den folgenden Zeilen lässt sich auf bekannte Art und Weise bildlich in Abbildung 20.9 verfolgen.

Nun zum eigentlichen Einsatzgebiet von std::weak_ptr, dem Aufbrechen von zyklischen Referenzen des std::shared_ptr. Bevor wir uns aber die Lösung anschauen, sollten wir zuerst eine zyklische Referenz bilden.

Die zyklische Referenz in Listing 20.14 ist nach folgendem Rezept gebaut.

Nehmen Sie zwei Knoten Node mit einem next-Zeiger vom Typ std::shared_ptr<Node>. Verbinden Sie die next-Zeiger der zwei Knoten miteinander, und Sie erhalten eine zyklische Referenz.

Das Listing 20.14 folgt der Struktur von Abbildung 20.10:

cycle.cpp

Node (Zeile 4) in Listing 20.14 ist der im Rezept zitierte Knoten. Dieser enthält einen Namen, einen Destruktor in Zeile 8, der anzeigt, wann er aufgerufen wurde. Der Knoten erhält darüber hinaus eine Elementfunktion, um den std::shared_ptr<Node> in Zeile 12 zu setzen, und eine Elementfunktion getCycle, die die ganze zyklische Referenz ausgibt. Zuerst werden in den Zeilen 42 und 43 die zwei Knoten instanziiert, anschließend werden deren Namen ausgegeben. Der Knoten head wird in Zeile 50 in einen std::shared_ptr<Node> verpackt, und dessen next–Zeiger wird auf den ebenfalls frisch verpackten Knoten tail in Zeile 51 gesetzt. Die zyklische Referenz lässt sich in der Ausgabe des Programms in Abbildung 20.11 direkt und indirekt erkennen.

Direkt, denn die Ausgabe von getCycle zeigt in Abbildung 20.11 sehr schön, dass zweimaliges Verfolgen des next-Zeigers wieder zum Ursprungszeiger zurückführt. Der Name und die Adresse des Ursprungszeigers sind in Abbildung 20.11 (head:0x604040) dargestellt, und dieses Wertepaar taucht in identischer Form im übernächsten Knoten auf. Indirekt, denn der Destruktor des Knotens wird nicht aufgerufen. Dass der Destruktor aufgerufen wird, zeigt das Auskommentieren der Zeilen 52 bis 58. Dadurch wird der Zyklus nicht geschlossen, und das Programm verhält sich anständig (Abbildung 20.12).

Die Lösung der zyklischen Referenz ist relativ einfach. Ein next-Zeiger vom Typ std::shared_ptr<Node> wird durch einen std::weak_ptr<Node> ausgetauscht. Der Komfort eines echten Smart Pointer std::shared_ptr<Node> ist natürlich dahin, denn um auf den nächsten std::shared_ptr<Node> mittels next zuzugreifen, muss der std::weak_ptr<Node> in einen std::shared_ptr<Node> konvertiert werden. Damit wird die Funktion getCycle noch komplizierter zu implementieren.

In Listing 20.15 folgt die angepasste Implementierung.

cycleBreak.cpp

01 #include <iostream>
02 #include <memory>
03
04 class Node{
05 public:
06   Node(const std::string& n):name(n){}
07
08   ~Node(){
09     std::cout << "destructor invoked" << std::endl;
10   }
11
12   void setWeakNext( std::shared_ptr<Node> n ){
13     weak_next= n;
14   }
15
16   void setSharedNext( std::shared_ptr<Node> n ){
17     shared_next= n;
18   }
19
20   std::string getName() const { return name; }
21
22   void getCycle() const{
23     std::cout << "this" << std::endl;
24     std::cout << "     (" << getName() << ":"
                 << (void*)this << ")" << std::endl;
25     std::cout << "this->next" << std::endl;
26     std::cout << "     (" << shared_next->getName()
                 << ":" << (void*)shared_next.get() << ")"
                 << std::endl;
27     if (std::shared_ptr<Node> next =
           shared_next->weak_next.lock()) {
28       std::cout << "this->next->next" << std::endl;
29       std::cout << "     (" << next->getName() << ":"
                   << (void*)next.get() << ")" << std::endl;
30     }
31   }
32
33 private:
34   std::string name;
35   std::weak_ptr<Node> weak_next;
36   std::shared_ptr<Node> shared_next;
37 };
38
39
40
41 int main(){
42
43   std::cout << std::endl;
44
45   {
46
47     // create the Nodes and give them names
48     Node* head= new Node("head");
49     Node* tail= new Node("tail");
50     std::cout << "head->getName(): " << head->getName()
                 << std::endl;
51     std::cout << "tail->getName(): " << tail->getName()
                 << std::endl;
52
53     std::cout << std::endl;
54
55     // create the shared pointer
56     std::shared_ptr<Node> spForHead(head);
57     head->setSharedNext(std::shared_ptr<Node>(tail));
58
59     // close the cycle
60     tail->setWeakNext(spForHead);
61
62     // show the cycle
63     spForHead->getCycle();
64
65     std::cout << std::endl;
66
67   }
68
69   std::cout << std::endl;
70
71 }
Listing 20.15 Aufbruch der zyklischen Referenzen durch std::weak_ptr

Sowohl die zyklische Referenz als auch den Aufruf der beiden Konstruktoren zeigt Abbildung 20.13.

weakPtrLock.cpp

Aufgabe 20-6

Versuchen Sie, einen std::weak_ptr mehrmals zu locken.

Der Rückgabewert von weak.lock() für einen std::weak_ptr weak wird als

expired()? shared_ptr<T>(): shared_ptr<T>(*this)

beschrieben. Verifizieren Sie, dass das Locken eines std::weak_ptr nur dann einen Default-initialisierten std::shared_ptr<T>() zurückgibt, wenn der weak_ptr verfallen (expired) ist. Dies ist genau dann der Fall, wenn der use_count von std::weak_ptr 0 ist. Lange Rede, kurzer Sinn: Ein std::weak_ptr kann öfter gelockt werden.

Lange vermisst, gibt es in C++11 nun die Möglichkeit, Hashtabellen einzusetzen. Hashtabellen sind unverzichtbare Container, wenn die Performance im Vordergrund steht. Neben Hashtabellen bietet C++11 aber noch weitere mächtige Container an: std::tuple erweitert das C++-Paar std::pair, das std::array vereinigt das Beste aus dem C-Array mit dem C++-Vektor, und die einfache verkettete Liste std::forward_list ist auf minimale Speicheranforderungen getrimmt.

<tuple>

std::tuple ist eine Verallgemeinerung des heterogenen STL-Containers std::pair. Beim std::tuple gilt die Einschränkung auf ein Paar nicht mehr, denn er kann beliebig viele Elemente annehmen. Leider ist es dem std::tuple anzusehen, dass er ein Template ist. Das zeigt sich beim Zugriff auf seine Elemente, und das zeigt auch die Iteration über das Tupel in Listing 20.16.

tupleInterface.cpp

01 #include <iostream>
02 #include <string>
03 #include <tuple>
04 #include <typeinfo>
05
06 class MyInt{
07 public:
08   MyInt(int i): val(i){}
09   int getVal() const{
10     return val;
11   }
12 private:
13   int val;
14 };
15
16 bool operator < (const MyInt& l, const MyInt& r){
17   return l.getVal() < r.getVal();
18 }
19
20 std::ostream& operator << (std::ostream& strm,
                              const MyInt& myIn){
21   strm << "MyInt(" << myIn.getVal() << ")";
22   return strm;
23 }
24
25
26 int main(){
27
28   std::cout << std::endl;
29
30   std::cout << std::boolalpha;
31
32   // creating tuples
33   std::tuple<int,double> tup0;
34   std::pair<int,int> pair(2011,2011.5);
35   tup0= pair;
36   std::tuple<std::string,int,float> tup1("tup1",3,4.17);
37   std::tuple<std::string,int,double> tup2("tup2",4,1.1);
38
39   // print the values
40   std::cout << "tup1: "  << std::get<0>(tup1) << ","
               << std::get<1>(tup1) << ","
               << std::get<2>(tup1) << std::endl;
41   std::cout << "tup2: "  << std::get<0>(tup2) << ","
               << std::get<1>(tup2) << ","
               << std::get<2>(tup2) << std::endl;
42
43   // compare them
44   std::cout << "tup1 < tup2: " << (tup1 < tup2)
               << std::endl;
45
46   std::cout << std::endl;
47
48   // modify a tuple value
49   std::get<0>(tup2)= "Tup2";
50
51   // print the values
52   std::cout << "tup1: "  << std::get<0>(tup1) << ","
               << std::get<1>(tup1) << ","
               << std::get<2>(tup1) << std::endl;
53   std::cout << "tup2: "  << std::get<0>(tup2) << ","
               << std::get<1>(tup2) << ","
               << std::get<2>(tup2) << std::endl;
54
55   // compare them
56   std::cout << "tup1 < tup2: " << (tup1 < tup2)
               << std::endl;
57
58   std::cout << std::endl;
59
60   // use MyInt
61   std::tuple<MyInt,int> tup3(MyInt(1),2011);
62   std::tuple<MyInt,int> tup4(MyInt(0),2011);
63
64   // print the values
65   std::cout << "tup3: "  << std::get<0>(tup3) << ","
               << std::get<1>(tup3) << std::endl;
66   std::cout << "tup4: "  << std::get<0>(tup4) << ","
               << std::get<1>(tup4) << std::endl;
67
68   std::cout << "tup3 < tup4: " << (tup3 < tup4)
               << std::endl;
69
70   std::cout << std::endl;
71
72   // modify a tuple value
73   std::get<0>(tup4)= MyInt(2011);
74
75   // print the values
76   std::cout << "tup3: "  << std::get<0>(tup3) << ","
               << std::get<1>(tup3) << std::endl;
77   std::cout << "tup4: "  << std::get<0>(tup4) << ","
               << std::get<1>(tup4) << std::endl;
78
79   std::cout << "tup3 < tup4: " << (tup3 < tup4)
               << std::endl;
80
81   std::cout << std::endl;
82
83 }
Listing 20.16 Das std::tuple-Interface

Listing 20.16 zeigt eine kleine Tour durch die wichtigsten std::tuple-Funktionen. Die Klasse MyInt in Zeile 6 ist die bekannte Hülle um den Datentyp int. Um ihn auf kleiner (<) vergleichen und ausgeben zu können, sind sowohl der Vergleichsoperator für kleiner als auch der Ausgabeoperator überladen. Dazu aber am Ende des Programms mehr. Zuerst werden in verschiedenen Varianten std::tuple erzeugt. In Zeile 33 wird ein Default-konstruiertes Tupel definiert, das in Zeile 35 durch ein std::pair initialisiert wird. Dabei wird das zweite Argument von int nach double konvertiert. Die Elemente der zwei folgenden Tupel tup1 und tup2 werden anschließend ausgegeben, und die Tupel werden verglichen. Auf das erste Element des Tupels tup1 lässt sich mithilfe des Aufrufs std::get<0> (tup1) sowohl lesend (Zeile 40) als auch schreibend (Zeile 49) zugreifen. Nachdem das erste Element von tup1 modifiziert wurde, gibt der Vergleich in Zeile 56 false aus. Das gleiche Spiel ist mit dem eigenen Datentyp MyInt möglich, da für ihn der entsprechende Vergleichsoperator und der Ausgabeoperator definiert wurden. In den Zeilen 61 und 62 werden dazu die zwei std::tuple tup3 und tup4 definiert. Die aufwendige Ausgabe und der Vergleich der std::tuple sind in Abbildung 20.14 dargestellt.

Die zwei Hilfsfunktionen std::make_tuple und std::tie vereinfachen den Umgang mit std::tuple. Werden diese praktischen Erzeugungsfunktionen mit dem neuen Schlüsselwort auto kombiniert, reduziert sich der Schreibaufwand auf das Notwendigste.

make_tuple

War std::make_pair in C++98 eine praktische Hilfsfunktion, um std::pair-Datentypen zu erzeugen, so ist es std::make_tuple für std::tuple. Diese Erzeugerfunktion ist ein Funktions-Template, das den Typ der Template-Parameter automatisch aus dem Argument ableitet. Vereinfacht gesagt, erzeugt ein Aufruf std::make_tuple(1,'a',3.14) dank automatischer Typableitung ein Tupel der Form std::tuple<type(1),type('a'),type(3.14)>(1,'a',3.14). Kommen die Refererenz-Wrapper std::ref oder std::cref für die Argumente der Tupel zum Einsatz, werden Referenzen oder konstante Referenzen erzeugt.

Deutlich einfacher ist dies mit der weiteren Hilfsfunktion std::tie, die ein Tupel erzeugt, das nur Referenzen auf Objekte hält. Wird der Rückgabewert von std::tie ignoriert, ist std::tie eine einfache Möglichkeit, ein bestehendes std::tuple in Variablen zu entpacken. Dabei können Argumente des zu entpackenden Tupels mit std::ignore ignoriert werden.

Genug der Worte. Listing 20.17 soll für Klarheit sorgen. Der Übersichtlichkeit halber werden in dem Beispiel nur Tupel von int-Datentypen verwendet.

helperTuple.cpp

01 #include <functional>
02 #include <iostream>
03 #include <tuple>
04
05 int main(){
06
07   std::cout << std::endl;
08
09   // make a tuple
10   auto tup1= std::make_tuple(1,2,3);
11
12   // print the values
13   std::cout << "std::tuple tup1: ("
               << std::get<0>(tup1) << ","
               << std::get<1>(tup1) << ","
               << std::get<2>(tup1) << ")" << std::endl;
14
15   std::cout << std::endl;
16
17   int first= 1;
18   int second= 2;
19   int third= 3;
20   int fourth= 4;
21
22   // create a tuple with references
23   auto tup2= std::make_tuple(
     std::cref(first),std::ref(second),std::ref(third),fourth);
24
25   // print the values
26   std::cout << "std::tuple tup2: (" << std::get<0>(tup2)
               << "," << std::get<1>(tup2) << ","
               << std::get<2>(tup2) << ","
               << std::get<3>(tup2) << ")" <<  std::endl;
27
28   std::cout << std::endl;
29
30   //change the values
31   // will not work, because of std::cref(first)
32   // std::get<0>(tup2)= 1001;
33   first= 1001;
34   std::get<1>(tup2)=1002;
35   third= 1003;
36   fourth= 1004;
37
38   // print the values
39   std::cout << "std::tuple tup2: (" << std::get<0>(tup2)
               << "," << std::get<1>(tup2)
               << "," << std::get<2>(tup2)
               << "," << std::get<3>(tup2) << ")" << std::endl;
40   std::cout << "global variables: " << first
               << " " << second << " " << third
               << " " << fourth << std::endl;
41
42   std::cout << std::endl;
43
44   first= 1;
45   second= 2;
46   third= 3;
47   fourth= 4;
48
49   // create tup3 and set the variables
50   auto tup3= std::tie(first,second,third,fourth)=
                std::make_tuple(1001,1002,1003,1004);
51
52   // print the values
53   std::cout << "std::tuple tup3: (" << std::get<0>(tup3)
               << "," << std::get<1>(tup3)
               << "," << std::get<2>(tup3)
               << "," << std::get<3>(tup3) << ")" << std::endl;
54   std::cout << "global variables: " << first << " "
               << second << " " << third << " "
               << fourth << std::endl;
55
56   std::cout << std::endl;
57
58   int a;
59   int b;
60
61   // bind the 2th and 4th argument to a and b
62   std::tie(std::ignore,a,std::ignore,b)= tup3;
63
64   // print the values
65   std::cout << "a: " << a << std::endl;
66   std::cout << "b: " << b << std::endl;
67
68   std::cout << std::endl;
69
70   // will also work for std::pair
71   std::tie(a,b)= std::make_pair(3001,3002);
72
73   // print the values
74   std::cout << "a: " << a << std::endl;
75   std::cout << "b: " << b << std::endl;
76
77   std::cout << std::endl;
78
79 }
Listing 20.17 Die Hilfsfunktionen std::make_tuple und std::tie

Das Erzeugen eines Tupels in Listing 20.17 geht in Zeile 10 dank std::make_pair und automatischer Typableitung mit auto schnell von der Hand. In Zeile 23 werden die Argumente von tup2 auf verschiedene Arten an die Variablen gebunden. first wird als konstante Referenz adressiert, second und third als Referenz. Lediglich fourth wird kopiert. Diese Bindungen bewirken, dass das erste Element nicht über das Tupel-Interface modifiziert werden kann (Zeile 32). Dagegen sind second und third sowohl über das Tupel-Interface std::get<1>(tup2)= 1001 in Zeile 33 als auch die Variable third modifizierbar. Die Ausgabe in Abbildung 20.15 zeigt, dass die Variablenzuweisung fourth=4 in Zeile 47 keine Auswirkung auf tup2 besitzt. Dies ist nicht verwunderlich, wurde das vierte Argument von tup2 in Zeile 23 doch mit Copy-Semantik versehen. std::tie in Zeile 50 erzeugt ein std::tuple aus Referenzen. Damit ist die Anwendung von std::ref auf die Tupel-Argumente nicht mehr notwendig. Zeile 50 hat mehr Aufmerksamkeit verdient. Durch std::make_tuple(1001,1002,1003,1004) lässt sich ein Tupel erzeugen. Dies wird std::tie(first,second,third,fourth) zugewiesen. std::tie erledigt zwei Aufgaben. Zuerst entpackt die Funktion das Tupel in die Variablen first, second, third und fourth. Anschließend gibt sie ein Tupel zurück, das Referenzen auf diese vier Variablen hält. Zuletzt wird das Tupel an die Variable tup3 gebunden.

Das von std::tie erzeugte Tupel muss aber an keine Variable gebunden werden. In Zeile 62 werden nur die Werte der Variablen a und b als reiner Seiteneffekt des Aufrufs des Funktions-Templates std::tie gesetzt. Da tup3 vier Argumente besitzt und nur zwei Argumente in dem Aufruf von std::tie verwendet werden, werden die überflüssigen Argumente durch std::ignore ignoriert. Dass std::tuple nur eine Verallgemeinerung von std::pair ist, zeigt ein weiteres Mal Zeile 71, denn std::tie kann den Rückgabewert von std::make_pair annehmen.

tuple_element und tuple_size

fourReturnValues.cpp

Aufgabe 20-7

Schreiben Sie eine Funktion, die vier heterogene Typen zurückgibt.

Die Funktion returnFourValues ist nicht vollständig implementiert. Vervollständigen Sie die Funktion und geben Sie ihre Werte aus.

??? returnFourValues(){
  int a= 5;
  double b= 10.1;
  std::string c= "test";
  bool c= true;
  return ??? a, b, c, d
}

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.

Aufgabe 20-8

Implementieren Sie die Funktion divmod, die für die Division von zwei Ganzzahlen den ganzen Anteil und den Rest der Division zurückgibt.

divmod ist eine bekannte Funktion aus Python. Aus der Dokumentation von Python: divmod(x, y) -> (div, mod). Sie können das Ergebnis der Operation als Tupel oder einfach nur als Paar zurückgeben.

tupleArrayComp.cpp

tupleArrayCompSolution.cpp

Aufgabe 20-9

std::array ist mit std::tuple verwandt.

Ist std::array ein sequenzieller Container, ist es auch entfernt verwandt mit std::tuple.

Portieren Sie Listing 20.19 auf std::array<int,3> und führen Sie das Programm aus.

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.

<array>

Ein bisschen detaillierter soll es aber schon sein. Der neue Standardcontainer std::array ist ein sequenzieller Container fester Länge, der wahlfreien Zugriff erlaubt. Damit schließt er genau die Lücke zwischen dem C-Array und dem C++-std::vector. Mit dem C-Array hat er gemein, dass er keinen zusätzlichen Speicher benötigt, und mit std::vector, dass er STL-konform ist. Selbst mit dem std::tuple teilt sich std::array einige Eigenschaften. So lässt sich ein Element mit der get-Elementfunktion referenzieren. Dazu im Listing 20.20 mehr.

arrayInterface.cpp

01 #include <algorithm>
02 #include <array>
03 #include <iostream>
04 #include <iterator>
05
06 const int NUM= 10;
07
08 int main(){
09
10   std::cout << std::endl;
11
12   std::cout << std::boolalpha;
13
14   // not value initialized
15   std::array<int,NUM> arr1;
16   std::cout << "arr1: ";
17   std::copy(arr1.begin(),arr1.end(),
       std::ostream_iterator<int>(std::cout, " "));
18
19   std::cout << std::endl;
20   std::cout << std::endl << "arr2:  ";
21
22   // value-initialization
23   std::array<int,NUM> arr2= {};
24   std::array<int,NUM>::const_iterator arrIt;
25   for( arrIt= arr2.begin(); arrIt != arr2.end(); ++arrIt){
26       std::cout << *arrIt << " ";
27   }
28
29   std::cout << std::endl;
30
31   std::array<int,NUM> arr3({{1,2,3,4}});
32   std::cout << std::endl << "arr3: ";
33   for ( auto a: arr3){
34     std::cout << a << " " ;
35   }
36
37   std::cout << std::endl;
38
39   // initializer list
40   std::array<int,NUM> arr4({{1,2,3,4,5,6,7,8,9,10}});
41   std::cout << std::endl << "arr4: ";
42   std::copy(arr4.rbegin(),arr4.rend(),
       std::ostream_iterator<int>(std::cout, " "));
43
44   std::cout << std::endl;
45
46   // get the size of arr4
47   double sum= std::accumulate(arr4.begin(),arr4.end(),0);
48   double mean= sum / arr4.size();
49   std::cout << "mean of a4: " << mean << std::endl;
50
51   // read and write
52   std::cout << "arr4[5]: " << arr4[5] << std::endl;
53   std::cout << "arr4.at(5): " << arr4.at(5) << std::endl;
54   arr4[5]= 2011;
55   std::cout << "arr4[5]: " << arr4[5] << std::endl;
56
57   // swap arrays
58   std::swap(arr1,arr4);
59   std::cout << std::endl << "arr4: ";
60     for ( auto a: arr4){
61       std::cout << a << " " ;
62     }
63
64   std::cout << std::endl;
65
66   // comparison
67   std::cout << "(arr1 < arr4): " << (arr1 < arr4 )
               << std::endl;
68
69   // tuple like
70   std::cout << "(arr4[0] == std::get<0>(arr4)): "
               << (arr4[0] == std::get<0>(arr4)) << std::endl;
71
72   std::cout << std::endl;
73
74 }
Listing 20.20 Das Interface von std::array

Die Verwandtschaft von std::array und std::vector geht so weit, dass, sieht man von kleinen Modifikationen ab, in Listing 20.20 std::array mit std::vector getauscht werden könnten. Ein std::array als STL-Container lässt sich auf verschiedene Weisen instanziieren. arr1 in Zeile 15 wird mit zehn Werten instanziiert. Dabei werden die Werte nicht initialisiert. Dies steht im Gegensatz zu arr2 in Zeile 23, denn hier werden durch die leere Initialisiererliste alle Elemente auf 0 initialisiert. Ist die Initialisiererliste zu kurz gewählt, trifft die Initialisierung mit 0 auf die restlichen Elemente zu. Neben der Instanziierung der std::array-Objekte variiert in Listing 20.20 die Ausgabe der einzelnen Elemente. In Zeile 17 kommt der Algorithmus std::copy zum Einsatz. Deutlich umständlicher ist da schon die klassische Form mit Iteratoren in Zeile 25. Komfort pur stellt die for-Schleife in Zeile 33 dar. Die letzte Variation besteht darin, die Elemente in Zeile 42 in der umgekehrten Reihenfolge auszugeben. std::array kennt im Gegensatz zum C-Array seine Länge. Dies nutzt die Berechnung des Mittelwerts in Zeile 48 aus. Lesenden und schreibenden Indexzugriff unterstützt std::array natürlich als sequenzieller Container. Der Unterschied zwischen dem Indexzugriff in Zeile 52 und dem at-Zugriff in Zeile 53 ist, dass Letzterer die Array-Grenzen überprüft. Natürlich lassen sich std::array-Objekte tauschen (Zeile 58) und vergleichen (Zeile 67). Vertraut sollte der Aufruf von std::get<0>(arr4) aus Zeile 70 wirken. Mit der gleichen Syntax lassen sich auch die Elemente des Tupels (siehe Abschnitt „Tupel“) referenzieren. Abbildung 20.17 zeigt insbesondere, dass die Elemente von arr1 nicht initialisiert sind.

Praxistipp

Ein array ist ein Aggregat, das ein Aggregat enthält.

Gewöhnungsbedürftig sind die doppelten Klammern {{ bzw. }} in der Initialisierung der std::array arr3 und arr4 in Listing 20.20, Zeilen 31 und 40, durch eine Initialisiererliste. Der Grund liegt in der Implementierung des std::array.

  • Ein std::array ist ein Aggregat, das ein Aggregat enthält.

Mit dieser Merkregel im Kopf lässt sich ein std::array richtig über eine Initialisiererliste initialisieren.

arrayAggregate.cpp

arrayViolation.cpp

Aufgabe 20-10

Greifen Sie über die Indexgrenzen des Arrays hinaus.

In bekannter Tradition mit std::vector und std::deque bietet std::array zwei Arten von Indexzugriffen an. Mit dem Indexoperator [] lassen sich die Elemente ohne Kontrolle der Containergrenzen referenzieren, mit der at-Elementfunktion werden die Containergrenzen zur Laufzeit überprüft. Schreiben Sie ein kleines Programm, das mit dem Indexoperator [] und der at-Elementfunktion über die Array-Grenzen hinaus auf Elemente zugreifen will.

<forward_list>

std::forward_list ist ein sequenzieller Container mit einem eingeschränkten Interface. Als einfach verkettete Liste benötigt sie nicht mehr Speicher als die entsprechende 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, sollte die einfach verkettete Liste in Erwägung gezogen werden. Dabei unterstützt std::forward_list vertraute Operationen wie das Dekrementieren eines Iterators –-It oder das Hinzufügen eines neuen Elements mit push_back nicht. Ihre minimalen Speicheranforderungen drücken sich auch darin aus, dass sie nicht die Anzahl ihrer Elemente speichert und diese über eine Methode zur Verfügung stellt. Während das Hinzufügen zu oder auch das Entfernen von Elementen der einfach verketteten Liste an ihrem Anfang direkt möglich ist, wird für diese Operationen im allgemeinen Fall ein Iterator benötigt. Manipulationen der std::forward_list beziehen sich in diesem allgemeinen Fall auf die dem Iterator folgenden Positionen.

In Listing 20.22 ist das Interface der std::forward_list in Aktion zu sehen.

forwardListManipulate.cpp

01 #include <algorithm>
02 #include <forward_list>
03 #include <iostream>
04
05 int main(){
06
07   std::cout << std::boolalpha << std::endl;
08
09   std::forward_list<int> myForList;
10
11   std::cout << "myForList.empty(): "
               << myForList.empty() << std::endl;
12   myForList.push_front(7);
13   myForList.push_front(6);
14   myForList.push_front(5);
15   myForList.push_front(4);
16   myForList.push_front(3);
17   myForList.push_front(2);
18   myForList.push_front(1);
19
20   std::cout << std::endl;
21
22   std::cout << "myForList: " << std::endl;
23   for (auto It= myForList.cbegin();It != myForList.cend();
          ++It) std::cout << *It << " ";
24   std::cout << "\n\n";
25
26   std::cout <<
     "myForList.erase_after(myForList.before_begin()): "
               << std::endl;
27   myForList.erase_after(myForList.before_begin());
28   std::cout<< "myForList.front(): " << myForList.front()
              << "\n\n";
29
30   std::forward_list<int>myForList2;
31   myForList2.insert_after(myForList2.before_begin(),1);
32   myForList2.insert_after(myForList2.before_begin()++,2);
33   myForList2.insert_after(
                            (myForList2.before_begin()++)++,3);
34   myForList2.push_front(1000);
35
36   std::cout << "myForList2: " << std::endl;
37   for (auto It= myForList2.cbegin();It != myForList2.cend();
          ++It) std::cout << *It << " ";
38   std::cout << "\n\n";
39   auto IteratorTo5=
       std::find(myForList.begin(),myForList.end(),5);
40   myForList.splice_after(IteratorTo5,std::move(myForList2));
41
42   std::cout << "myForList.splice_after(IteratorTo5,
                   std::move(myForList2)): "
               << std::endl;
43   for (auto It= myForList.cbegin();It != myForList.cend();
          ++It) std::cout << *It << " ";
44   std::cout << "\n\n";
45
46   myForList.sort();
47
48   std::cout << "myForList.sort(): " << std::endl;
49   for (auto It= myForList.cbegin();It != myForList.cend();
          ++It) std::cout << *It << " ";
50   std::cout << "\n\n";
51
52   myForList.reverse();
53
54   std::cout << "myForList.reverse(): " << std::endl;
55   for (auto It= myForList.cbegin();It != myForList.cend();
          ++It) std::cout << *It << " ";
56   std::cout << "\n\n";
57
58   myForList.unique();
59
60   std::cout << "myForList.unique(): " << std::endl;
61   for (auto It= myForList.cbegin();It != myForList.cend();
          ++It) std::cout << *It << " ";
62   std::cout << "\n";
63
64   std::cout << std::endl;
65
66 }
Listing 20.22 Arbeiten mit einer std::forward_list

Ein paar Anmerkungen noch zu Listing 20.22, bevor in Abbildung 20.18 die Ausgabe des Programms folgt. Vertraut sollten die ersten Zeilen des Listings wirken. In Zeile 40 wird durch den Ausdruck myForList.splice_after(IteratorTo5,std::move(myForList2)) myForList2 an myForList nach der Position IteratorTo5 angehängt. Die Elemente von myForList2 werden transferiert. Das Sortieren (Zeile 46), Umkehren der Reihenfolge (Zeile 52) oder das Entfernen von Duplikaten (Zeile 58) der Elemente einer verketteten Liste sind Elementfunktionen von std::forward_list. Bei unique gilt es zu beachten, dass nur Duplikate entfernt werden, die aufeinanderfolgen.

forwardListSize.cpp

Aufgabe 20-11

Bestimmen Sie die Anzahl der Elemente einer std::forward_list.

forwardListRemoveIf.cpp

Aufgabe 20-12

Entfernen Sie Elemente, die ein Prädikat erfüllen, aus einer std::forward_list.

Füllen Sie eine std::forward_list<int> mit den Ganzzahlen von 0 bis 9. Entfernen Sie im ersten Schritt das Element, das den Wert 8 besitzt. Entfernen Sie im zweiten Schritt alle Elemente, die kleiner als 3 oder größer als 5 sind. Geben Sie jeweils die Ergebnisse zur Kontrolle aus.

Ohne die Elementfunktionen remove und remove_if (std::forward_list, 2011) ist die Aufgabe relativ umständlich zu lösen.

<unordered_set>

<unordered_map>

Hashtabellen, auch bekannt unter den Namen Dictionary oder assoziatives Array, sind aus dem Leben eines Programmierers nicht wegzudenken – erlauben sie es doch, einfach und performant Werte über assoziierte Schlüssel abzufragen. Dass C++ keine Hashtabellen besaß, wog nicht so schwer, denn mit der Datenstruktur std::map stand ein Container zur Verfügung, der einer Hashtabelle sehr ähnlich ist. Einerseits ist std::map mächtiger als eine Hashtabelle, da seine Schlüssel geordnet sind, andererseits hängt seine Zugriffszeit auf die Schlüssel logarithmisch von der Anzahl der Schlüssel ab. Hier spielt aber die neue Hashtabelle ihre wahre Stärke aus, denn ihre Zugriffszeit auf die Schlüssel ist unabhängig von der Anzahl der Schlüssel. Den bekannten assoziativen Containern std::set, std::multiset, std::map und std::multimap stellt C++11 die ungeordneten assoziativen Container std::unordered_set, std::unordered_multiset, std::unordered_map und std::unordered_multimap gegenüber. Die Tabellen in Kapitel 5 im Abschnitt „Hashtabellen“ helfen, Ordnung in die verschiedenen assoziativen Container zu bringen. Wer mit den bekannten assoziativen Containern aus C++98 vertraut ist, kann Listing 20.23 und Listing 20.24 gern überspringen, denn die neuen ungeordneten assoziativen Container bieten nahezu das gleiche Interface wie die bekannten geordneten assoziativen Container an.

unordSet.cpp

01 #include <iostream>
02 #include <set>
03 #include <unordered_set>
04
05 int main(){
06
07   std::cout << std::endl;
08
09   // constructor
10   std::unordered_multiset<int>multiSet
       {1,2,3,4,5,6,7,8,9,8,7,6,5,4,3,2,1};
11   std::unordered_set<int> uniqSet
       (multiSet.begin(),multiSet.end());
12
13   // show the difference
14   std::cout << "multiSet: ";
15   for(auto m : multiSet) std::cout << m << " ";
16
17   std::cout << std::endl;
18
19   std::cout << "uniqSet: ";
20   for(auto s : uniqSet) std::cout << s << " ";
21
22   std::cout << std::endl << std::endl;
23
24   // insert elements
25   multiSet.insert(-1000);
26   uniqSet.insert(-1000);
27
28   std::set<int> mySet{-5,-4,-3,-2,-1};
29   multiSet.insert(mySet.begin(),mySet.end());
30   uniqSet.insert(mySet.begin(),mySet.end());
31
32   // show the difference
33   std::cout << "multiSet: ";
34   for(auto m : multiSet) std::cout << m << " ";
35
36   std::cout << std::endl;
37
38   std::cout << "uniqSet: ";
39   for(auto s : uniqSet) std::cout << s << " ";
40
41   std::cout << std::endl << std::endl;
42
43   // search for elements
44   auto it= uniqSet.find(5);
45   if ( it != uniqSet.end()){
46     std::cout << "uniqSet.find(5): " << *it << std::endl;
47   }
48
49   std::cout << "multiSet.count(5): " << multiSet.count(5)
               << std::endl;
50
51   std::cout << std::endl;
52
53   // remove
54   int numMulti= multiSet.erase(5);
55   int numUniq= uniqSet.erase(5);
56
57   std::cout << "Erased " << numMulti
               << " times 5 from multiSet." << std::endl;
58   std::cout << "Erased " << numUniq
               << " times 5 from uniqSet." << std::endl;
59
60   // all
61   multiSet.clear();
62   uniqSet.clear();
63
64   std::cout << std::endl;
65
66   std::cout << "multiSet.size(): " << multiSet.size()
               << std::endl;
67   std::cout << "uniqSet.size(): " << uniqSet.size()
               << std::endl;
68
69   std::cout << std::endl;
70
71 }
Listing 20.23 Das Interface von std::unordered_set und std::unordered_multiset

In Listing 20.23 werden zwei ungeordnete assoziative Container instanziiert, deren Schlüssel kein Wert zugeordnet ist. Dabei kann multiSet in Zeile 10 mehrere gleiche Schlüssel besitzen, uniqSet in Zeile 11 dagegen nicht. Das zeigt die Ausgabe in Abbildung 20.19, denn alle Duplikate in uniqSet sind nicht mehr vorhanden. Dass die Elemente in den beiden Containern sortiert sind, ist rein zufällig. Dies ist in der nächsten Ausgabe zu sehen, nachdem ein paar weitere Zahlen mit der Elementfunktion insert in den Zeilen 25 bis 30 hinzugefügt wurden. Die Elementfunktion find in Zeile 44 gibt im Erfolgsfall einen Iterator auf das zu suchende Element zurück und einen Iterator auf uniqSet.end() im Misserfolgsfall. Wer es genauer wissen will, kann sich mit der Elementfunktion count in Zeile 49 die Anzahl der Elemente ermitteln lassen. Durch die Elementfunktion erase in Zeile 54 werden alle adressierten Elemente entfernt. Dies schließt die mehrfach vorkommenden Schlüssel im Fall von muliSet.erase(5) in Zeile 54 ein. Die Funktion clear löscht alle Schlüssel, sodass die Länge der beiden ungeordneten assoziativen Arrays danach 0 beträgt.

std::map ist im klassischen C++ der mit Abstand am häufigsten eingesetzte assoziative Container, verhält er sich doch fast wie eine Hashtabelle. std::unordered_map hingegen ist eine Hashtabelle. Seine Schlüssel sind nicht sortiert, und jedem Schlüssel ist ein Wert zugeordnet. Der entscheidende Punkt ist aber, dass die Suche nach einem Schlüssel in der Hashtabelle unabhängig von der Anzahl der Schlüssel ist.

Listing 20.24 zeigt die Anwendung von std::unordered_map zusammen mit dem std::unordered_multimap, der mehrere gleiche Schlüssel unterstützt.

unordMap.cpp

01 #include <iostream>
02 #include <map>
03 #include <unordered_map>
04
05 int main(){
06
07   std::cout << std::endl;
08
09   long long home= 497074123456;
10   long long mobile= 4916046123356;
11
12   // constructor
13   std::unordered_multimap<std::string,long long> multiMap
       {{"grimm",home},{"grimm",mobile},{"jaud-grimm",home}};
14   std::unordered_map<std::string,int> uniqMap
       {{"bin",1},{"root",0},{"nobody",65834},{"rainer",1000}};
15
16   // show the unordered maps
17   std::cout << "multiMap: ";
18   for(auto m : multiMap) std::cout << '{' << m.first
                                     << ',' << m.second << '}';
19
20   std::cout << std::endl;
21
22   std::cout << "uniqMap: ";
23   for(auto u : uniqMap) std::cout << '{' << u.first
                                     << ',' << u.second << '}';
24   std::cout << std::endl;
25
26   std::cout << std::endl;
27
28   // insert elements
29   long long work= 4970719754513;
30
31   multiMap.insert({"grimm",work});
32   // will not work
33   // multiMap["grimm-jaud"]=4916012323356;
34
35   uniqMap["lp"]=4;
36   uniqMap.insert({"sshd",71});
37
38   std::map<std::string,int> myMap
       {{"ftp",40},{"rainer",999}};
39   uniqMap.insert(myMap.begin(),myMap.end());
40
41   // show the unordered maps
42   std::cout << "multiMap: ";
43   for(auto m : multiMap) std::cout << '{' << m.first
                                     << ',' << m.second << '}';
44
45   std::cout << std::endl;
46
47   std::cout << "uniqMap: ";
48   for(auto u : uniqMap) std::cout << '{' << u.first
                                     << ',' << u.second << '}';
49   std::cout << std::endl;
50
51
52   std::cout << std::endl;
53   // search for elements
54
55   // only grimm
56   auto iter= multiMap.equal_range("grimm");
57   std::cout << "grimm: ";
58   for(auto itVal= iter.first; itVal !=iter.second;++itVal){
59     std::cout << itVal->second << " ";
60   }
61
62   std::cout << std::endl;
63
64   std::cout << "multiMap.count(grimm): "
               << multiMap.count("grimm") << std::endl;
65
66   auto it= uniqMap.find("root");
67   if ( it != uniqMap.end()){
68     std::cout << "uniqMap.find(root): " << it->second
                 << std::endl;
69     std::cout << "uniqMap[root]: " << uniqMap["root"]
                 << std::endl;
70   }
71
72   // will create a new entry
73   std::cout << "uniqMap[notAvailable]: "
               << uniqMap["notAvailable"] << std::endl;
74
75   std::cout << std::endl;
76
77   // remove
78   int numMulti= multiMap.erase("grimm");
79   int numUniq= uniqMap.erase("rainer");
80
81   std::cout << "Erased " << numMulti
               << " times grimm from multiMap." << std::endl;
82   std::cout << "Erased " << numUniq
               << " times rainer from uniqMap." << std::endl;
83
84   // all
85   multiMap.clear();
86   uniqMap.clear();
87
88   std::cout << std::endl;
89
90   std::cout << "multiMap.size(): " << multiMap.size()
               << std::endl;
91   std::cout << "uniqMap.size(): " << uniqMap.size()
               << std::endl;
92
93   std::cout << std::endl;
94
95 }
Listing 20.24 Das Interface von std::unordered_map und std::unordered_multimap

Listing 20.24 folgt einer ähnlichen Struktur wie Listing 20.23. In den Zeilen 13 und 14 werden zwei Hashtabellen deklariert. Dabei kann multiMap mit mehreren identischen Schlüsseln umgehen, uniqMap hingegen nicht. Dies ergibt durchaus Sinn, soll doch multiMap zu einer Person alle Telefonnummern speichern und uniqMap die eindeutige Beziehung zwischen einem Benutzer und seiner ID zur Verfügung stellen. Beide Datenstrukturen lassen sich auf gewohnte Weise als Pärchen ausgeben. Dabei wird mit m.first der Schlüssel und mit m.second der Wert in Zeile 18 referenziert. Auch das Hinzufügen neuer Paare fühlt sich wie bei allen assoziativen Containern an. Für eine Hashtabelle typisch, ist der Zugriff auf uniqMap über den Indexoperator in Zeile 35 möglich. Dies gilt natürlich auch für den lesenden Zugriff. Ein std::map kann dazu verwendet werden, einem std::unorderd_map neue Elemente hinzuzufügen. Da der Schlüssel "rainer" in Zeile 38 in uniqMap vorhanden ist, wird dessen ursprünglicher Wert überschrieben. Dies zeigt am besten die Ausgabe in Abbildung 20.20. Das Suchen nach Schlüsseln und deren assoziierten Werten ist die Stärke von Hashtabellen. In den Zeilen 56 bis 60 werden alle Werte ausgegeben, die den Schlüssel "grimm" besitzen. Das ist zugegeben ein bisschen umständlich. Durch auto iter= multiMap.equal_range("grimm") wird ein Iterator zurückgegeben, der in Zeile 58 benutzt wird, um über alle Werte zu iterieren. Dabei erleichtert auto das Leben des Anwenders enorm. Tatsächlich ist iter vom Typ

std::pair<
          std::unordered_map<std::string,long long>::iterator,
          std::unordered_map<std::string,long long>::iterator>.

Deutlich einfacher in der Anwendung ist das Zählen der Telefonnummern in Zeile 64, die dem Schlüssel "grimm" zugeordnet sind. Ob ein Schlüssel existiert, lässt sich in Zeile 66 mit der Elementfunktion find ermitteln. Mit it->second (Zeile 68) oder dem Indexoperator (Zeile 69) kann anschließend auf den Wert zugegriffen werden. Ist der Schlüssel nicht vorhanden, wird ein Schlüssel/Wert-Paar erzeugt und der Hashtabelle hinzugefügt. Für den Wert wird der Standardkonstruktor ausgeführt. Genau dieses Verhalten ist in Zeile 73 zu sehen, sodass uniqMap um das Paar (std::string("notAvailable"),int()) erweitert wird. Der Rest des Listings sollte keine Überraschungen beinhalten. Durch multiMap.erase("grimm") bzw. uniqueMap.erase("rainer") werden alle Paare aus den Hashtabellen mit den angegebenen Schlüsseln entfernt. Die Elementfunktion clear entfernt in bekannter Manier alle Schlüssel/Wert-Paare.

Die zentrale Komponente einer Hashtabelle ist die sogenannte Hashfunktion. Diese Funktion bildet die Schlüssel auf einen Index, den sogenannten Hashwert, ab. Der wesentliche Unterschied zwischen einem Array und einem assoziativen Array ist, das Letzterer nicht nur natürliche Zahlen als Indizes erlaubt. Andersherum betrachtet, verhält sich ein assoziatives Array, das natürliche Zahlen als Indizes erlaubt und deren Hashfunktion die Identität ist, wie ein Array.

Eine Frage bleibt aber noch offen. Welche Datentypen können als Schlüssel verwendet werden? C++11 unterstützt von Hause aus die folgenden Datentypen:

Soll ein eigener Datentyp als Schlüssel verwendet werden, haben die Objekte dieses Datentyps zwei Charakteristiken zu erfüllen. Zum einen muss der Hashwert zur Verfügung stehen, zum anderen müssen die Objekte auf Gleichheit == vergleichbar sein. Der alte Bekannte MyInt wird in Listing 20.25 als Schlüssel verwendet.

hashClass.cpp

01 #include <iostream>
02 #include <ostream>
03 #include <unordered_map>
04
05 struct MyInt{
06   MyInt(int v):val(v){}
07   bool operator== (const MyInt& other) const {
08     return val == other.val;
09   }
10   int val;
11 };
12
13 struct MyHash{
14   std::size_t operator()(MyInt m) const {
15     std::hash<int> hashVal;
16     return hashVal(m.val);
17   }
18 };
19
20 struct MyAbsHash{
21   std::size_t operator()(MyInt m) const {
22     std::hash<int> hashVal;
23     return hashVal(abs(m.val));
24   }
25 };
26
27 struct MyEq{
28   bool operator() (const MyInt& l, const MyInt& r) const {
29     return abs(l.val) ==  abs(r.val);
30   }
31 };
32
33 std::ostream& operator << (std::ostream& strm,
                              const MyInt& myIn){
34   strm << "MyInt(" << myIn.val << ")";
35   return strm;
36 }
37
38 int main(){
39
40   std::cout << std::endl;
41
42   std::hash<int> hashVal;
43
44   // a few hash values
45   for ( int i= -2; i <= 1 ; ++i){
46     std::cout << "hashVal(" << i << "): " << hashVal(i)
                 << std::endl;
47   }
48
49   std::cout << std::endl;
50
51   typedef std::unordered_map<MyInt,int,MyHash> MyIntMap;
52
53   std::cout << "MyIntMap: ";
54   MyIntMap myMap{{MyInt(-2),-2},{MyInt(-1),-1},
                    {MyInt(0),0},{MyInt(1),1}};
55
56   for(auto m : myMap) std::cout << '{' << m.first << ','
                                   << m.second << '}';
57
58   std::cout << std::endl << std::endl;
59
60   typedef std::unordered_map<MyInt,int,MyAbsHash,MyEq>
       MyAbsMap;
61   std::cout << "MyAbsMap: ";
62   MyAbsMap myAbsMap{{MyInt(-2),-2},{MyInt(-1),-1},
                       {MyInt(0),0},{MyInt(1),1}};
63
64   for(auto m : myAbsMap) std::cout << '{' << m.first
                                     << ',' << m.second << '}';
65
66   std::cout << std::endl << std::endl;
67
68   std::cout << "myAbsMap[MyInt(-2)]: "
               << myAbsMap[MyInt(-2)] << std::endl;
69   std::cout << "myAbsMap[MyInt(2)]: "
               << myAbsMap[MyInt(2)] << std::endl;
70   std::cout << "myAbsMap[MyInt(3)]: "
               << myAbsMap[MyInt(3)] << std::endl;
71
72   std::cout << std::endl << std::endl;
73
74 }
Listing 20.25 Einen eigenen Datentyp als Schlüssel in einem assoziativen Array verwenden

Für den Datentyp MyInt (Zeile 5) in Listing 20.25 ist die Gleichheit über seinen Datentyp val definiert. Dieser einfache Ansatz wird auch auf die Hashfunktion von MyInt angewandt. Aber zuerst der Reihe nach. In der main-Funktion wird in Zeile 42 ein Klassen-Template hashVal definiert, das sich wie eine Funktion benutzen lässt. Dies ist in der Zeile 46 sehr schön zu sehen, da diese Funktion zu jeder Zahl ihren Hashwert ausgibt. In Abbildung 20.21 sind die numerischen Werte dargestellt. Interessant ist, dass die natürlichen Zahlen 0 und 1 auf sich selbst abgebildet werden. Dies trifft nicht auf negative Ganzzahlen zu. Der Typ MyIntMap in Zeile 51 verwendet MyInt als Schlüssel. Als drittes Template-Argument erhält MyIntMap die Hashfunktion. Diese Klasse MyHash in Zeile 13 ist ein aufrufbares Objekt, denn der operator() ist implementiert. Dabei nimmt dieser einen Typ MyInt an und gibt als Hashwert den Hashwert der Variablen val zurück. Auch die zweite Bedingung erfüllt MyInt. Durch das Überladen des Operators == in Zeile 7 sind seine Objekte auf Gleichheit vergleichbar. Der Instanziierung von myMap und deren Ausgabe (Abbildung 20.21) steht nichts mehr entgegen.

MyAbsMap in Zeile 60 erhält ein viertes Template-Argument. Das Funktionsobjekt MyEq ist in MyAbsMap für den Vergleich auf Gleichheit zuständig. Das Besondere von MyIntMap gegenüber MyAbsMap ist, dass in MyAbsMap der absolute Wert von val für die Berechnung des Hashwerts und den Gleichheitsvergleich verwendet werden soll. Dies ist der Grund dafür, dass in dem Funktionsobjekt MyAbsHash der Hashwert von hash(abs(m.val))) in Zeile 23 zurückgegeben wird und dass in Zeile 29 der Gleichheitsvergleich abs(l.val) == abs(r.val) lautet. Für die einfache Ausgabe der assoziativen Arrays ist noch der Ausgabeoperator in Zeile 33 definiert. Betrachten wir nun Abbildung 20.21, fällt auf, dass MyAbsMap ein Wertepaar weniger besitzt. Was ist der Grund? Da der Hashwert von MyInt(1) identisch mit dem von MyInt(-1) ist, wenn der absolute Wert verwendet wird, tritt das bekannte Verhalten von Hashtabellen auf. Das Wertepaar (MyInt(1),1) wird durch (MyInt(-1),-1) überschrieben.

Aber nicht nur die Hashfunktion, auch der Test auf Gleichheit ignoriert das Vorzeichen. In den Zeilen 68 und 69 ergibt die Abfrage an myAbsMap[MyInt(-2)] und MyAbsMap[MyInt(2)] jeweils den Wert 2. Das Bemerkenswerte ist, dass der Hashwert von MyInt(-2) gleich dem von MyInt(2) ist. Dies bewirkt, dass kein neues Element (MyInt(2),2) zu myAbsMap hinzugefügt wird. Den feinen Unterschied zeigt die Abfrage myAbsMap[MyInt(3)] in Zeile 70. MyInt(3) ist kein Element von myAbsMap, sodass der assoziative Container um ein neues Paar (MyInt(3),int()) erweitert wird.

Für den Anwender ist die Geschichte zu Hashtabellen in C++11 hier zu Ende. C++11 erlaubt es aber, wie so oft, seine Datenstrukturen weiter zu tunen. Die Hashtabelle macht da keine Ausnahme. Dazu ist ein tieferes Verständnis von Hashtabellen notwendig.

Buckets, Kapazität und Ladefaktor

mapHashComparison.cpp

Aufgabe 20-13

Vergleichen Sie die Zugriffszeit von std::map und std::unordered_map.

Legen Sie zwei große assoziative Container vom Datentyp std::map<int,int> und std::unordered_map<int,int> an und greifen Sie mit dem Indexoperator auf ein Zehntel der bestehenden Elemente zufällig zu. Vergrößern Sie sukzessive die Größe der assoziativen Container. Vergleichen Sie die Zugriffszeiten auf die Elemente. Entspricht das Ergebnis Ihren Erwartungen?

Bei 10.000.000 Elementen und 1.000.000 Zugriffen ist der Zugriff auf meiner Plattform für std::unordered_map um den Faktor 15 schneller als der für std::map.

bucketInfo.cpp

Aufgabe 20-14

Geben Sie den Inhalt jedes Bucket aus.

Ein std::unordered_map<int,std::string> myDict verrät viel über sich. Neben der Anzahl der Buckets mit myDict.bucket_count() lässt sich die Anzahl der Elemente in einem Bucket mit myDict.bucket_size(b) für das b-te Bucket ermitteln. Darüber hinaus lassen sich die Elemente des b-ten Bucket mit den zwei Iteratoren myDict.begin(b) und myDict.end(b) ausgeben.

Definieren Sie eine Hashtabelle std::unordered_map<int,std::string> myDict und füllen Sie sie mit ein paar Schlüssel/Wert-Paaren. Bestimmen Sie für myDict die Anzahl der Buckets, die Anzahl der Elemente jedes Bucket und iterieren Sie zum Abschluss über jedes Bucket.

Die Algorithmen std::all_of, std::any_of und std::none_of prüfen logische Zusicherungen auf Bereichen.

std::copy_if und std::copy_n sind weitere Kopieralgorithmen auf Bereichen.

std::iota hilft beim schnellen Erzeugen von Werten.

Es geht weiter mit std::partition_copy, std::is_partitioned und std::partition_point für den Umgang mit Partitionen sowie std::is_heap und std::is_heap_until für den Umgang mit der Datenstruktur Heap (Heap, 2011).

Auch zum Sortieren gibt es zwei neue Algorithmen: std::is_sorted und std::is_sorted_until.

Praktisch sind auch die zwei Algorithmen std::minmax und std::minmax_element, die ein Paar (min,max) zurückgeben. Während std::minmax auf einem Wertepaar oder einer std::initializer_list aufgerufen werden kann und das Minimum/Maximum-Wertepaar ermittelt, wirkt std::minmax_element auf einem Bereich und ermittelt ein Minimum/Maximum-Iteratorenpaar.

Viele der Algorithmen sind in Listing 20.27 im Einsatz zu sehen.

newAlgorithm.cpp

01 #include <algorithm>
02 #include <array>
03 #include <deque>
04 #include <iostream>
05 #include <iterator>
06 #include <list>
07 #include <vector>
08
09 bool even_(const int& i){
10           return ((i % 2) == 0);
11 }
12
13 int main() {
14
15   std::cout << std::endl;
16
17   std::cout << std::boolalpha;
18
19   // increase each element by 1
20   std::vector<int> myVec(20);
21   std::iota(myVec.begin(),myVec.end(),1);
22
23   std::cout << "myVec:";
24   for (auto i: myVec) std::cout << i << " ";
25   std::cout << std::endl << std::endl;
26
27   // test a predicate on a range
28   std::cout << "one even in myVec: "
               << std::any_of(myVec.begin(),myVec.end(),even_)
               << std::endl;
29   std::cout << "all even in myVec: "
               << std::all_of(myVec.begin(),myVec.end(),even_)
               << std::endl;
30   std::cout << "all even in myVec with lambda: "
               << std::all_of(myVec.begin(),myVec.end(),
                           [](int i){ return (i % 2) == 0; })
               << std::endl;
31   std::cout << "none even in myVec with lambda: "
               << std::none_of(myVec.begin(),myVec.end(),
                            [](int i){ return (i % 2) == 0; })
               << std::endl;
32
33   std::cout << std::endl;
34
35   // copying all odd element to std::cout
36   std::cout << "all odd elements: ";
37   std::copy_if(myVec.begin(), myVec.end(),
        std::ostream_iterator<int>(std::cout, " "),
        [](int a){ return a % 2;} );
38
39   std::cout << std::endl;
40
41   // copying the first 10 element
42   std::cout << "The first 10 elements: ";
43   std::copy_n(myVec.begin(),10,
       std::ostream_iterator<int>(std::cout, " "));
44
45   std::cout << std::endl << std::endl;
46
47   std::list<int> allOdd;
48   std::deque<int> allEven;
49
50   // odd ints to list allOdd
51   // even ints to deque allEven
52   std::partition_copy(myVec.begin(),myVec.end(),
       std::back_inserter(allEven),std::back_inserter(allOdd),
       even_);
53
54   std::cout << "allOdd: ";
55   for (auto o: allOdd) std::cout << o << " ";
56
57   std::cout << std::endl;
58
59   std::cout << "allEven: ";
60   for (auto e: allEven) std::cout << e << " ";
61
62   std::cout << std::endl;
63
64   // test, if partitioned
65   std::cout << "Partition a < 10: "
              << std::is_partitioned(myVec.begin(),myVec.end(),
                 [](int a){ return a < 10;}) << std::endl;
66
67   std::cout << std::endl;
68
69   // get the partition point
70   std::cout << "Partition Point for a < 10: " <<
       *(std::partition_point(myVec.begin(),myVec.end(),
       [](int a){ return a < 10;})) << std::endl;
71
72   std::cout << std::endl;
73
74   // test, if sorted
75   std::cout << "Is sorted (ascending): "
               << std::is_sorted(myVec.begin(),myVec.end())
               << std::endl;
76
77   std::cout << "Is sorted (descending): "
               << std::is_sorted(myVec.begin(),myVec.end(),
                  [](int a, int b ){return a > b;})
               << std::endl;
78
79   myVec.push_back(-10);
80   myVec.push_back(100);
81   myVec.push_back(2011);
82
83   std::cout << std::endl;
84
85   std::cout << "myVec:";
86   for (auto i: myVec) std::cout << i << " ";
87   std::cout << std::endl << std::endl;
88
89   std::cout << "Is sorted until: "
        << *(std::is_sorted_until(myVec.begin(),myVec.end()))
        << std::endl;
90
91   std::cout << std::endl;
92
93 }
Listing 20.27 Die neuen Algorithmen

Die Funktion even_ (Zeile 9) in Listing 20.27 ist ein einfaches Prädikat, das auf Anfrage ermittelt, ob das Argument gerade ist. Der Vektor myVec wird durch den neuen Algorithmus std::iota in Zeile 21 initialisiert. Das erste Element des Vektors erhält den Wert des Funktionsarguments 1, jedes weitere Element wird um 1 inkrementiert. Anschließend wird auf myVec bestimmt, ob ein Element std::any_of gerade ist (Zeile 28), ob alle Elemente std::all_of gerade sind (Zeilen 29 und 30) und ob kein Element std::none_of gerade ist (Zeile 31). Neben dem Prädikat kommt eine Lambda-Funktion in den Zeilen 30 und 31 zum Einsatz. Die drei praktischen Funktionen sollten dem einen oder anderen aus Haskell oder auch Python vertraut sein. Praktisch ist auch der Algorithmus std::copy_if in Zeile 37, der es erlaubt, nur die Elemente auf std::cout zu kopieren, die das Prädikat in Form einer anonymen Funktion erfüllen. std::copy_n erlaubt es hingegen, eine feste Anzahl von Elementen zu kopieren. Das Kopieren wird mit std::partition_copy in Zeile 52 noch mächtiger. Diesmal ist das Ziel der Kopieraktion nicht std::cout, sondern zwei Container vom Typ std::list und std::deque. Vollkommen generisch lassen sich die Elemente, die das Prädikat erfüllen, auf den Container allEven schieben. Der Rest landet in allOdd. Generisch ist, dass es weder dem Container noch der for-Schleife anzusehen ist, welcher Containertyp der Operation zugrunde liegt. Generisch ist, dass durch den Iterator-Adapter std::back_inserter in Zeile 52 die Elemente direkt auf den Container geschoben werden können.

Ob ein Bereich partitioniert ist, lässt sich leicht durch std::is_partitioned in Zeile 65 ermitteln. std::partition_point liefert darüber hinaus noch den Iterator iter auf das erste Element der zweiten Teilmenge. Durch *iter lässt sich auf sein Element zugreifen. std::is_sorted beantwortet die Frage, ob ein Bereich aufsteigend sortiert ist. Durch die Parametrisierung des Algorithmus mit der Vergleichsfunktion [](int a, int b) {return a>b;} ist schnell getestet, ob ein Bereich absteigend sortiert ist. Nachdem in Zeile 79 das Element -10 auf den Container geschoben wurde, ist dieser nicht mehr aufsteigend sortiert. Bis zu welcher Stelle dieser sortiert ist, das beantwortet der neue Algorithmus std::is_sorted. std::is_sorted gibt wieder einen Iterator zurück, der auf das erste Element zeigt, das der Sortierreihenfolge widerspricht. In Abbildung 20.25 lässt sich der Programmlauf auf der Konsole nachvollziehen.

C++11 bringt noch ein paar weitere praktische Algorithmen mit, die den Umgang mit den Containern der STL vereinfachen. Die Container in C++11 können mit Initialisiererlisten initialisiert werden, unterstützen die Move-Semantik und erzeugen auf Anfrage einen konstanten Iterator mit den Methoden cbegin und cend bzw. einen reversen konstanten Iterator mit crbegin und crend.

emplace, emplace_back und emplace_front

Das direkte Erzeugen eines neuen Datentyps in einem Container ist mit emplace, emplace_back und emplace_front, die sich im Wesentlichen wie die bekannten insert, push_back und push_front verhalten, direkt möglich. Der feine Unterschied ist, dass bei emplace_back im Gegensatz zu push_back kein unnötiges Konstruieren eines temporären Objekts und das anschließende Kopieren notwendig sind. Listing 20.28 zeigt die Methoden im Einsatz.

emplace.cpp

In Listing 20.28 ist die ressourcenschonende Anwendung der emplace- Funktionen zu sehen. In den Zeilen 24 und 25 werden die Argumente direkt an den Konstruktor von MyVal durchgereicht. Entsprechend wird in Zeile 36 der Wert MyVal(15) für den Schlüssel 2 in myMap eingefügt. In Abbildung 20.26 ist die Ausführung des Programms zu sehen.

shrink_to_fit

Die Verbesserungen gehen weiter. In C++ gibt es das bekannte Idiom »shrink-to-fit«, um einen Vektor std::vector<int> vecInt auf seine tatsächlich benötigte Größe zu reduzieren: std::vector<int>(vecInt).swap(vecInt). Der Name dieses Tricks stand Pate für die neuen shrink_to_fit-Algorithmen, die für std::string, std::deque und std::vector zur Verfügung stehen. Der Aufruf ist aber nicht bindend.

at

Wird in einer Map std::map<int,std::string> myMap nach einem Element myMap[2011] gefragt, das es nicht gibt, erzeugt die C++-Laufzeit implizit ein neues Paar (2011,std::string()) und fügt dieses in myMap ein. Für den Wert wird der Standardkonstruktor aufgerufen.

Ein Aufruf myMap.at(2012) in C++11 wirft eine Ausnahme, falls der Schlüssel 2012 in myMap nicht vorhanden ist.

Aufgabe 20-15

Was ist ein Heap?

In Listing 20.27 findet sich kein Beispiel zu den neuen C++11-Algorithmen std::is_heap und std::is_heap_until. Die erste Frage ist natürlich: Was ist ein Heap? Die kurze Antwort lautet: Ein Heap ist ein binärer Baum, bei dem das Vaterelement immer größer ist als seine Kindelemente. In Abbildung 20.27 ist ein Heap in Baumstruktur dargestellt.

Die lange Antwort lässt sich im Wikipedia-Artikel zur Heap-Datenstruktur (Heap, 2011) nachlesen.

heap.cpp

Aufgabe 20-16

Erzeugen Sie einen Heap.

Durch die C++-Algorithmen std::make_heap wird ein Heap erzeugt. Mit std::sort_heap lassen sich die Elemente des Heap sortiert ausgeben. Die C++11-Algorithmen std::is_heap und std::is_heap_until beantworten die Frage, ob eine Sequenz ein Heap ist.

minMax.cpp

Aufgabe 20-17

Ermitteln Sie den minimalen und den maximalen Wert des sequenziellen Containers std::deque.

std::minmax_element lässt sich über eine Vergleichsfunktion parametrisieren. Ermitteln Sie das minimale und das maximale Element eines std::deque<std::string>. Variieren Sie im zweiten Schritt die Aufgabenstellung, indem Sie die Länge des Strings als Vergleichskriterium verwenden.

shrinkToFit.cpp

Aufgabe 20-18

Wenden Sie die neue Methode shrink_to_fit auf einem std::vector an.

In (Becker, Working Draft, Standard for Programming Language C++ (N3242), 2011) ist über shrink_to_fit für std::vector zu lesen: »... shrink_to_fit is a non-binding request to reduce capacity() to size().« Legen Sie einen std::vector an, dessen Kapazität größer als seine tatsächliche Größe ist. Geben Sie die Kapazität und seine tatsächliche Größe vor und nach shrink_to_fit aus. Besitzt der Aufruf von shrink_to_fit auf Ihrer Plattform eine Auswirkung?

<functional>

Der Anwendungsbereich von std::bind ist die Definition von aufrufbaren Einheiten aus Funktionsobjekten oder Funktionen. Diese aufrufbaren Einheiten können direkt ausgeführt oder in einem Funktionsobjekt gespeichert werden. Hier tritt std::function in Aktion, denn sie nimmt die Funktionsobjekte von std::bind an. std::function-Objekte fühlen sich wie Daten an. Sie können kopiert oder als Ein- oder Ausgabe einer Funktion verwendet werden. Da std::bind es erlaubt, Funktionen teilweise zu evaluieren, ist die beliebte Technik der funktionalen Programmierung nun auch in C++11 zu Hause: Evaluieren Sie eine Funktion teilweise mit std::bind und binden Sie das Ergebnis an eine neue Funktion mit std::function.

std::function nimmt die Funktionsobjekte von std::bind an und bindet sie unter einem neuen Namen. Der Funktionstyp der anzunehmenden Funktion wird durch das Template-Argument tempArg von std::function <tempArg> spezifiziert. Tabelle 20.1 stellt ein paar Funktionstypen tempArg dar.

std::function-Objekte können wie gewöhnliche Werte kopiert oder auch als Callback verwendet werden. In Listing 20.29 ist die perfekte Zusammenarbeit von std::bind und std::function zu sehen.

bindAndFunction.cpp

01 #include <algorithm>
02 #include <functional>
03 #include <iostream>
04 #include <iterator>
05 #include <vector>
06
07 double divMe(double a, double b){
08   return double(a/b);
09 }
10
11 using namespace std::placeholders;
12
13 int main(){
14
15   std::cout << std::endl;
16
17   // invoking the function object directly
18   std::cout << "1/2.0= " << std::bind(divMe,1,2.0)()
               << std::endl;
19
20   // placeholders for both arguments
21   std::function<double(double,double)> myDivBindPlaceholder=
       std::bind(divMe,_1,_2);
22   std::cout << "1/2.0= " << myDivBindPlaceholder(1,2.0)
               << std::endl;
23
24   // placeholders for both arguments, swap the arguments
25   std::function<double(double,double)>
       myDivBindPlaceholderSwap= std::bind(divMe,_2,_1);
26   std::cout << "1/2.0= " << myDivBindPlaceholderSwap(2.0,1)
               << std::endl;
27
28   // placeholder for the first argument
29   std::function<double(double)> myDivBind1St=
       std::bind(divMe,_1,2.0);
30   std::cout<< "1/2.0= " << myDivBind1St(1) << std::endl;
31
32   // placeholder for the second argument
33   std::function<double(double)> myDivBind2Nd=
       std::bind(divMe,1.0,_1);
34   std::cout << "1/2.0= " << myDivBind2Nd(2.0) << std::endl;
35
36   // copy all element to std::cout, which are bigger than 10
37   std::vector<int>
       myVec{5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20};
38   std::copy_if(myVec.begin(),myVec.end(),
       std::ostream_iterator<int>( std::cout," "),
       std::bind( std::greater <int>(),_1,10));
39
40   std::cout << std::endl;
41   std::cout << std::endl;
42
43 }
Listing 20.29 Zusammenspiel von std::bind und std:function

Currying

std::bind in Listing 20.29 definiert in verschiedenen Variationen ein Funktionsobjekt. In Zeile 18 werden die Argumente direkt an divMe gebunden, und die abschließenden Klammern rufen das Funktionsobjekt auf. Im Gegensatz hierzu sind die Ausdrücke _1 und _2 in den std::bind-Ausdrücken (Zeilen 21, 25, 29 und 33) Platzhalter, die ihre Argumente erst beim Aufruf in der jeweils nächsten Zeile erhalten. Durch diese Platzhalter wird erreicht (Zeile 29), dass aus einer Funktion, die zwei Argumente erwartet, eine Funktion erzeugt wird, die nur noch ein Argument benötigt. Diese Technik ist unter dem Namen Currying (siehe Exkurs: Currying) aus der funktionalen Programmierung sehr bekannt. Der std::function-Aufruf gibt dem neu erzeugten Funktionsobjekt einen Namen. Dabei definiert das Template-Argument <double(double)> den Funktionstyp des Arguments – eine Funktion, die ein double annimmt und ein double zurückgibt. Relativ schwierig zu lesen ist der Ausdruck std::bind( std::greater <int>(),_1,10), denn hier wird ein spezielles Funktionsobjekt erklärt, ein Prädikat. Ein Prädikat gibt nur true oder false zurück. In diesem Fall gibt es genau dann true zurück, wenn die Zahl größer als 10 ist. Die Zahlen aus myVec, die größer als 10 sind, werden direkt nach std::cout kopiert. Genau diese Ausgabe ist in Abbildung 20.28 zu sehen.

Dies waren schon die wesentlichen Punkte zum Zusammenspiel von std::bind und std::function. Ein paar Details folgen noch.

Platzhalter

Auf den ersten Blick wirken die Platzhalter in Listing 20.29 nicht vertraut. Voll ausgeschrieben sind sie ein bisschen sperrig: std::placeholders::_1. Dabei bezeichnen sie die Argumente, die das aus std::bind resultierende Funktionsobjekt erhält. Das erste Argument des resultierenden Funktionsobjekts wird an std::placeholders::_1 gegeben, das zweite an std::placeholders::_2, das dritte an ... Ich denke, das Prinzip ist klar. Es hängt von der Implementierung ab, wie viele Platzhalter sie definiert. Ein Blick in die Implementierung verrät: Der GCC 4.7 (GCC 4.7, 2011) definiert 29 Platzhalter, der Microsoft-Visual C++-Compiler VC10 zehn Platzhalter.

Elementfunktionen

Nicht nur Funktionen und Funktionsobjekte, auch Methoden lassen sich mit std::bind extrahieren und als freie Funktionen anbieten. Dieses Extrahieren gilt nicht nur für die Methoden, sondern auch für den Zustand des Objekts. Listing 20.30 zeigt diese Magie in der Anwendung.

bindFuncObject.cpp

Family (Zeile 5) in Listing 20.30 stellt Familienobjekte zur Verfügung. Im Konstruktor wird der Familienname gesetzt, die Methode getName (Zeile 9) fügt zum Familiennamen den Vornamen hinzu und gibt ihn aus. Das Objekt grimm in Zeile 22 gibt den vollständigen Namen in der folgenden Zeile aus. Bis hierher nichts Besonderes. Das beginnt mit Zeile 28. Hier wird das Objekt mann vom Typ Family erzeugt. Das Objekt mann besitzt einen Zustand, denn der Familienname wurde im Konstruktoraufruf gesetzt. Dieses Objekt wird in Zeile 29 an mannCrea gebunden. Die Argumente von std::bind sind nicht ganz einfach zu lesen. Auf den Zeiger auf die Methode &Family::getName folgen das Objekt mann und der Platzhalter std::placeholders::_1. Das Template-Argument std::string <(const std::string&)> entspricht dabei der Signatur der Methode getName. Damit steht der Objektzustand in der freien Funktion mannCrea zur Verfügung und kann in den Funktionsaufrufen in den Zeilen 31, 32 und 33 angewandt werden. Die Ausgabe des Programms folgt in Abbildung 20.29.

Praxistipp

Ziehen Sie auto und Lambda-Funktionen bind und function vor.

Für das einfache Erzeugen von neuen Funktionsobjekten aus bestehenden Funktionen oder auch das Kopieren von Funktionsobjekten bietet C++11 zwei Wege an. Der eine Weg führt über die Bibliotheksfunktionen std::bind und std::function, der andere über auto und Lambda-Funktionen. Da stellt sich natürlich die Frage: Welchen Weg soll man wählen? Meist sind die Erweiterungen der Kernsprache einfacher zu schreiben und zu lesen. Ein paar Codeschnipsel sollen dies veranschaulichen.

Beide Funktionsobjekte beschreiben Prädikate, die für einen Eingabewert entscheiden, ob dieser größer als 10 ist.

Wird das resultierende Funktionsobjekt an eine Variable gebunden, wird der Unterschied noch deutlicher.

Beim nächsten Beispiel ist die Implementierung mit Funktionen der Standard Template Library meines Erachtens sehr verständnisresistent. Dabei lässt sich der Algorithmus einfach beschreiben: Kopiere alle Elemente von myVec nach std::cout, die größer als 9 und kleiner als 16 sind.

Der entscheidende Punkt in Listing 20.33 ist, dass sich das Prädikat mit einer Lambda-Funktion in einem Ausdruck beschreiben lässt, während mehrere Funktionsobjekte bei der STL-Implementierung ineinander verschachtelt werden müssen.

Insbesondere Programmierer, die mit den funktionalen Ideen vertraut sind, werden sich bei automatischer Typableitung und Lambda-Funktionen sofort zu Hause fühlen.

bindLamdaComparison.cpp

Aufgabe 20-19

Löschen Sie alle Elemente aus einem Vektor, die das Prädikat aus Listing 20.33 erfüllen.

Der Vektor soll vom Typ std::vector<int> sein. Lösen Sie die Aufgabe in zwei Variationen.

Definieren Sie das Prädikat durch

  • std::bind und speichern Sie es in std::function.

  • eine Lambda-Funktion und verwenden Sie auto, um es zu speichern.

Wenden Sie anschließend das Prädikat mit den Algorithmen der Standard Template Library an und geben Sie den reduzierten Vektor zur Kontrolle aus.

countAlphabetFunctional.cpp

Aufgabe 20-20

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

Zählen Sie die Häufigkeit der Buchstaben des Alphabets in einem Text, und dies ohne Berücksichtigung der Klein- oder Großschreibung. Geben Sie die Buchstaben nach ihrer Häufigkeit sortiert aus.

Das war eine Aufgabenstellung im Kapitel zu regulären Ausdrücken, aber das hier ist eine neue Aufgabe, die es gilt, mithilfe von std::function und std::bind zu lösen. Der Algorithmus std::for_each aus der Standard Template Library hilft Ihnen, das Problem zu lösen.

Lösen Sie die Aufgabe und vergleichen Sie die Lösung mit der Lösung in Kapitel 19 im Abschnitt „regex_iterator“ die auf regulären Ausdrücken aufbaut.