Open Source im professionellen Einsatz
Linux-Magazin 02/2016

Modernes C++ in der Praxis – Folge 26

Doppelte Packung

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.

1278

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.

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.

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.

Diesen Artikel als PDF kaufen

Express-Kauf als PDF

Umfang: 3 Heftseiten

Preis € 0,99
(inkl. 19% MwSt.)

Linux-Magazin kaufen

Einzelne Ausgabe
 
Abonnements
 
TABLET & SMARTPHONE APPS
Bald erhältlich
Get it on Google Play

Deutschland

Ähnliche Artikel

  • C++

    Die wichtigsten Neuerungen im neuen C++17-Standard finden in den Bibliotheken statt: der leichtgewichtige String-Wrapper "string_view", die parallelisierten Algorithmen der STL, die Dateisystem-Bibliothek oder die praktischen Datentypen "std::optional" und "std::any".

  • C++

    Implizite Typkonvertierungen sind eine der Ursachen für undefiniertes Verhalten in C- und C++-Programmen. In Kurzform bedeutet dies, dass solche Programme alles Mögliche tun dürfen und unvorhersehbar reagieren. C++-Routinier Rainer Grimm zeigt, wie C++-Entwickler diese Falle umgehen.

  • C++11

    In C++11 lässt sich manches prägnanter formulieren als in klassischem C++. Dieser Artikel zeigt, wie die neue Range-basierte For-Schleife und die automatische Typableitung dabei helfen.

  • C++11

    Was dem Perl-Programmierer altvertraut ist, fehlte C++ bisher. Der neue Sprachstandard C++11 bringt reguläre Ausdrücke ins Spiel, mit denen sich Textmuster beschreiben und finden lassen.

  • C++11

    Der Smart Pointer gilt als der klügste seiner Art in C++11. Sein Fachgebiet ist die elegante Garbage Collection, manchmal muss aber sein kleiner Bruder Weak Pointer mithelfen.

comments powered by Disqus

Ausgabe 10/2017

Digitale Ausgabe: Preis € 6,40
(inkl. 19% MwSt.)

Artikelserien und interessante Workshops aus dem Magazin können Sie hier als Bundle erwerben.