Aus Linux-Magazin 02/2016

Modernes C++ in der Praxis – Folge 26

Die Referenz-Wrapper bilden eine kleine, aber feine neue Bibliothek in C++11. Diese Objekte verhalten sich wie Referenzen, der Artikel erklärt die Details.

Eigentlich ist alles ganz einfach: Ein Referenz-Wrapper ist ein Objekt, das sich wie eine Referenz verhält. Anders als diese lässt es sich aber kopieren. Damit decken Referenz-Wrapper zwei Anwendungsfälle ab, die im klassischen C++ fehlen. Zum einen erlauben sie es, kopierbare Instanzen von Klassen mit Referenz-Wrappern zu erzeugen. Zum anderen kommen Referenz-Wrapper in Containern der Standard Template Library zum Einsatz.

Dabei drängt sich die Frage auf, wieso C++ Objekte, die Referenzen enthalten, nicht kopieren kann. Der Kopierprozess umfasst standardmäßig nicht nur das Objekt, sondern auch alle zugehörigen Elemente. Dafür erzeugt der Compiler automatisch Copy-Konstruktoren oder Copy-Zuweisungsoperatoren für jede Klasse sowie einige weitere Methoden.

In zwei der automatisch erzeugten Methoden ruft er den Copy-Konstruktor sowie den Copy-Zuweisungsoperator seiner Elemente auf, zu denen auch seine Referenz gehört. Da aber eine Referenz über keine Copy-Semantik [1] verfügt, beschwert sich der Compiler und weigert sich standhaft, das Objekt zu kopieren. Diese Einschränkungen gelten aber ab C++11 dank der Referenz-Wrapper nicht mehr. Listing 1 zeigt dies an zwei Klassen. Instanzen der Klasse »Bad« (Zeilen 5 bis 10) lassen sich nicht kopieren, Instanzen der Klasse »Good« (Zeilen 12 bis 23) dagegen schon. Die Referenz-Wrapper kommen über den Header »functional« ins Spiel.

Noch eine Bemerkung zur Klasse »Bad« : Listing 1 kommentiert den Aufruf des Copy-Zuweisungsoperators in Zeile 34 aus, da der Compiler diese Operation nicht unterstützt.

Die Klasse »Good« besitzt indes den Referenz-Wrapper

std::reference_wrapper<std::string> message

der einen »std::string« kapselt. Die Methode »getMessage()« (Zeile 15) gibt den Wert des Wrappers zurück, »changeMessage()« (Zeile 18) setzt den Wert neu. Über die Methode »get()« greifen Funktionen direkt auf den Inhalt der Referenz-Wrapper zu (Zeilen 16, 19).

In den Zeilen 39 und 40 deklariert die »main()« -Funktion die Instanzen »g1« und »g2« . Der erste entscheidende Ausdruck ist der Copy-Zuweisungsoperator der Referenz-Wrapper (»g2= g1« , Zeile 47). Anschließend besitzen beide Objekte den gleichen Wert. Die »get()« -Methode des Referenz-Wrappers erlaubt es aber auch, seinen Inhalt neu zu setzen (Zeile 54). Für weniger prosaische Naturen verdeutlicht Abbildung 1, was das Programm nach dem Ausführen tut.

Abbildung 1: Ein Objekt mit Referenz-Wrappern kopieren.

Abbildung 1: Ein Objekt mit Referenz-Wrappern kopieren.

Listing 1

Objekte mit einem Referenz-Wrapper kopieren

01 #include <functional>
02 #include <iostream>
03 #include <string>
04
05 class Bad{
06 public:
07   Bad(std::string& s):message(s){}
08 private:
09   std::string& message;
10 };
11
12 class Good{
13 public:
14   Good(std::string& s):message(s){}
15   std::string getMessage(){
16     return message.get();
17   }
18   void changeMessage(std::string s){
19     message.get()= s;
20   }
21 private:
22   std::reference_wrapper<std::string> message;
23 };
24
25 int main(){
26
27   std::cout << std::endl;
28
29   std::string bad1{"bad1"};
30   std::string bad2{"bad2"};
31
32   Bad b1(bad1);
33   Bad b2(bad2);
34   //b1= b2;
35
36   std::string good1{"good1"};
37   std::string good2{"good2"};
38
39   Good g1(good1);
40   Good g2(good2);
41   std::cout << "g1.getMessage(): " << g1.getMessage() << std::endl;
42   std::cout << "g2.getMessage(): " << g2.getMessage() << std::endl;
43
44   std::cout << std::endl;
45
46   std::cout << "g2= g1" << std::endl;
47   g2= g1;
48   std::cout << "g1.getMessage(): " << g1.getMessage() << std::endl;
49   std::cout << "g2.getMessage(): " << g2.getMessage() << std::endl;
50
51   std::cout << std::endl;
52
53   g1.changeMessage("veryGood");
54   std::cout << "g1.changeMessage(\"veryGood\")" << std::endl;
55   std::cout << "g1.getMessage(): " << g1.getMessage() << std::endl;
56   std::cout << "g2.getMessage(): " << g2.getMessage() << std::endl;
57
58   std::cout << std::endl;
59
60 }

Störrische Container

Referenzen lassen sich nicht in Containern der Standard Template Library verwenden, weil das klassische C++ einen Vektor der Form »std::vector<int&> myIntRefVector« , der eine Referenz als Elementtyp enthält, nicht zulässt. Vielmehr müssen die Elemente eines Containers die Copy-Semantik [1] unterstützen. Das tun sie nur, wenn der Copy-Zuweisungsoperator und der Copy-Konstruktor existieren. Für Referenzen trifft beides nicht zu.

Mit dem Referenz-Wrapper gilt das dank einer Doppelpack-Lösung nicht mehr. C++11 verpackt ein Objekt in einem Referenz-Wrapper und diesen wiederum in einem Container. Listing 2 zeigt, wie so eine Verpackung aussieht.

Listing 2

Referenz-Wrapper in Containern verwenden

01 #include <functional>
02 #include <iostream>
03 #include <vector>
04
05 int main(){
06
07   std::cout << std::endl;
08   //std::vector<int&> myIntRefVector;
09
10   int a= 0;
11   int b= 0;
12   int c= 0;
13
14   std::vector< std::reference_wrapper<int> > myIntRefVector= {std::ref(a),std::ref(b),std::ref(c)};
15   for (auto b: myIntRefVector ) std::cout << b << " ";
16
17   std::cout << std::endl;
18
19   b=2011;
20   for (auto b: myIntRefVector ) std::cout << b << " ";
21
22   std::cout << "\n\n";
23
24 }

Damit der Compiler das Programm übersetzt, muss der Entwickler Zeile 8 auskommentieren. In Zeile 14 taucht der Referenz-Wrapper auf. Die Hilfsfunktion »std::ref()« , die Referenz-Wrapper auf Variablen erzeugt, legt den Vektor mit Hilfe einer Initialisiererliste schnell an.

Die Ausgabe des Programms (Abbildung 2) demonstriert die Referenz-Semantik des Vektors. Zeile 19 setzt nicht nur die referenzierte Variable »b« auf »2011« , sondern auch das entsprechende Element des Vektors »myIntRefVector« (Zeile 20).Fehlen jetzt nur noch Erklärungen zu den zwei praktischen Funktionen »std::cref()« und »std::ref()« .

Abbildung 2: Referenz-Wrapper in Containern verwenden.

Abbildung 2: Referenz-Wrapper in Containern verwenden.

Verpacke mich

Um einen konstanten oder einen nicht konstanten Referenz-Wrapper auf einer Variablen einfach zu erzeugen, eignen sich die beiden Funktionen »std::cref()« und »std::ref()« . Wie leicht das mit ihrer Hilfe geht, zeigt Listing 3. Zuerst zu den Aufrufen der Funktion »invokeMe()« in den Zeilen 5 und 9. Die erste Version erwartet eine Referenz auf einen String, die zweite eine konstante Referenz. Dabei steuern »std::ref()« beziehungsweise »std::cref()« (Zeilen 24 und 25) genau, welche überladene Funktion zum Einsatz kommen soll.

Listing 3

Die Hilfsfunktionen std::cref() und std::ref()

01 #include <functional>
02 #include <iostream>
03 #include <string>
04
05 void invokeMe(std::string& s){
06   std::cout << s << ": not const " << std::endl;
07 }
08
09 void invokeMe(const std::string& s){
10   std::cout << s << ": const " << std::endl;
11 }
12
13 template <typename T>
14 void doubleMe(T t){
15   t *=2;
16 }
17
18 int main(){
19
20   std::cout << std::endl;
21
22   std::string s{"string"};
23
24   invokeMe(std::ref(s));
25   invokeMe(std::cref(s));
26
27   std::cout << std::endl;
28
29   int i=1;
30   std::cout << "i: " << i << std::endl;
31
32   doubleMe(i);
33   std::cout << "doubleMe(i): " << i << std::endl;
34
35   doubleMe(std::ref(i));
36   std::cout << "doubleMe(std::ref(i)): " << i << std::endl;
37
38   std::cout << std::endl;
39
40 }

Besonders interessant ist das Funktions-Template »doubleMe()« der Zeilen 13 bis 16. Es nimmt einen Wert »t« vom Typ »T« an und verdoppelt ihn. Damit das Ergebnis der Multiplikation außerhalb des Funktionskörpers bereitsteht, benötigt das Funktions-Template eine Referenz. Fehlt diese, verdoppelt sich der Wert der Variablen wie in Zeile 33 nicht.

Retter sind erneut Referenz-Wrapper in Form der globalen Funktion »std::ref()« (Zeile 36). Der Aufruf »doubleMe(std::ref(i))« verdoppelt den Wert der Variablen. Den Beweis liefert das ausgeführte Programm (Abbildung 3).

Abbildung 3: Die Hilfsfunktionen »std::ref()« und »std::cref()« erzeugen einfach Referenz-Wrapper auf Variablen.

Abbildung 3: Die Hilfsfunktionen »std::ref()« und »std::cref()« erzeugen einfach Referenz-Wrapper auf Variablen.

Es folgen bessere Zeiten

Beeindruckend geht es auch im nächsten Artikel weiter, der die neue Zeitbibliothek aufs Korn nimmt. Sie ist ähnlich praktisch wie die Referenz-Wrapper. Sie beherrscht vor allem zwei Domänen: Sie dient als Grundlage, um in Multithreading-Programmen Threads schlafen zu schicken oder den Lockversuch eines Mutex auf eine bestimme Zeitdauer zu begrenzen. Und sie hilft dabei, die Ausführungszeit eines Programmabschnitts auf einfache Weise zu messen.

Doch weil vor der Kür die Pflicht steht, erklärt der kommende Artikel zunächst die Konzepte Zeitpunkt, Zeitdauer und Zeitgeber im Detail. Sind die erst bekannt, lässt sich die Zeitbibliothek effektiv einsetzen.

Infos

  1. Rainer Grimm, “Rasch verschoben”: Linux-Magazin 12/12, S. 96

Der Autor

Rainer Grimm arbeitet als Software-Architekt und Gruppenleiter bei der Metrax GmbH in Rottweil. Besonders die Software der hauseigenen Defibrillatoren ist ihm eine Herzensangelegenheit. Seine Bücher “C++11 für Programmierer”, “C++” und “C++11-Standardbibliothek” sind bei O’Reilly erschienen.

DIESEN ARTIKEL ALS PDF KAUFEN
EXPRESS-KAUF ALS PDFUmfang: 3 HeftseitenPreis €0,99
(inkl. 19% MwSt.)
LINUX-MAGAZIN KAUFEN
EINZELNE AUSGABE Print-Ausgaben Digitale Ausgaben
ABONNEMENTS Print-Abos Digitales Abo
TABLET & SMARTPHONE APPS Readly Logo
E-Mail Benachrichtigung
Benachrichtige mich zu:
0 Kommentare
Älteste
Neuste Beste Bewertung
Inline Feedbacks
Alle Kommentare anzeigen
Nach oben