C++: const Type&&

von Hubert Schmid vom 2014-03-09

Zu den Spracherweiterungen von C++11 gehören die sogenannten R‑Value-Referenzen, die insbesondere die Move-Semantik unterstützen. Vereinfacht gesagt bedeutet eine R‑Value-Referenz, dass niemand sonst das referenzierte Objekt noch verwendet. Beziehungsweise umgekehrt: Das referenzierte Objekt darf ohne Rücksicht auf anderen Code manipuliert werden. Das ist äußerst hilfreich, weil Referenzen mit dieser Eigenschaft sehr häufig übergeben werden, was der Übersetzer in den meisten Fällen auch automatisch erkennt.

Soweit ist alles klar. Doch häufig wird die Frage aufgeworfen, wozu dann const Type&& gut sei – also R‑Value-Referenzen auf unveränderliche Objekte. Tatsächlich ist diese Kombination etwas merkwürdig und bietet kaum Einsatzmöglichkeiten. Doch zumindest einen sinnvollen Verwendungszweck gibt es.

Referenzen auf temporäre Objekte

Ich versuche es an einem Beispiel zu erklären. Das folgende Listing enthält die Definition einer Klasse für Funktionsobjekte, um zu überprüfen, ob ein bestimmter Wert in einer vorgegeben Menge enthalten ist. Die Datenstruktur std::set<int> wird dabei als const-Referenz übergeben, um unnötige Kopieroperationen zu verhindern.

class exists { const std::set<int>& _values; public: explicit exists(const std::set<int>& values) : _values{values} { } bool operator()(int value) const { return _values.count(value) > 0; } };

Das Ganze sieht auf den ersten Blick zwar vernünftig aus. Doch das Problem daran ist, dass es viel zu einfach ist, die Klasse falsch zu verwenden. So ist beispielsweise das Verhalten des folgenden, unscheinbaren Codes undefiniert:

exists is_prime{{2, 3, 5, 7, 11, 13, 17, 19}}; std::cout << is_prime(42) << '\n';

Der Fehler ist schnell erklärt: Beim Aufruf des Konstruktors wird ein temporäres Objekt vom Typ std::set<int> erzeugt und an die const-Referenz des Konstruktors gebunden. Der Konstruktor merkt sich die Referenz in einer Member-Variablen, um sie später zu verwenden. Jedoch wird das temporäre Objekt bereits am Ende der Anweisung wieder gelöscht, und der folgende Zugriff führt sehr wahrscheinlich zu einem unerwünschten Programmabbruch.

Um dieses Problem zu vermeiden, überlädt man in C++11 am einfachsten den Konstruktor für temporäre Objekte. Dieser Konstruktor wird mit = delete deklariert, und führt bei der versehentlichen Verwendung zu einem Übersetzungsfehler. Konkret sieht die Deklaration wie folgt aus:

explicit exists(const std::set<int>&& values) = delete;

In diesem Fall ist es tatsächlich sinnvoll, eine R‑Value-Referenz auf einen mit const deklarierten Typ zu verwenden. Dadurch ist der Konstruktor für alle temporären Objekte anwendbar – selbst für die Fälle, in denen temporäre Objekte const sind. Richtig ist es in jedem Fall, und Nachteile birgt es keine.

Fügt man diesen Konstruktor hinzu und übersetzt die beiden obigen Anweisungen nochmals, so spuckt Clang 3.5 die folgende Fehlermeldung aus:

demo.cpp:20:12: error: call to deleted constructor of 'exists' exists is_prime{{2, 3, 5, 7, 11, 13, 17, 19}}; ^ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ demo.cpp:11:14: note: 'exists' has been explicitly marked deleted here explicit exists(const std::set<int>&& values) = delete; ^ 1 error generated.

Fazit: Es gibt also auch für const R‑Value-Referenzen einen sinnvollen Verwendungszweck – zwar nicht zur Realisierung einer Funktionalität, aber zumindest zur frühzeitigen Erkennung und Vermeidung von Programmierfehlern. Ein Werkzeug, das viel zu häufig vernachlässigt wird.