C++11 Defaulted & Deleted Functions

von Hubert Schmid vom 2011-10-16

Mit der Version C++11 finden einige größere und viele kleinere Verbesserungen Einzug in die Sprache C++. In diesem Artikel werfe ich einen genaueren Blick auf die Möglichkeiten der sogenannten defaulted und deleted Funktionen.

Defaulted Special Functions

In Objekt-orientierten Programmiersprachen ist es üblich, dass der Compiler bestimmte Funktionen automatisch erzeugt, wenn der Benutzer nicht selbst entsprechende Funktionen definiert. Während in Java das nur für den Default-Konstruktor geschieht, der erzeugt wird falls der Programmierer keinen Konstruktor definiert, gibt es in C++ bereits eine Reihe dieser speziellen Funktionen. Neben dem Default-Konstruktor gehören der Destruktor, der Copy-Konstruktor sowie der Zuweisungsoperator dazu. C++11 erweitert die Liste noch um die entsprechenden Funktionen mit Move-Semantik.

Die Regeln für die automatische Erzeugung sind allerdings nicht trivial. Um den Entwickler mehr Kontrolle darüber zu geben, bietet C++11 nun die Möglichkeit, den Compiler explizit anzuweisen, bestimmte Default-Funktionen selbst zu implementieren. Dafür wird die Syntax =default verwendet, um am einfachsten kann ich sie am Default-Konstruktor zeigen.

class foobar { std::string _name; public: /* Because a constructor has been declared, the compiler * does not generate the default constructor ... */ explicit foobar(std::string name); /* NOTE: ... unless it is explicitly told to do so. */ foobar() = default; };

Der Default-Konstruktor ist in der Regel sehr einfach selbst zu implementieren. Die gleiche Syntax funktioniert aber auch für die anderen speziellen Funktionen, die oben bereits erwähnt wurden, und spätestens bei den Copy- und Move-Konstruktoren macht sich der Vorteil bemerkbar. Darüber hinaus ist für zukünftige Erweiterungen vorgesehen, mit diesem Mechanismus auch ganz andere Funktionen zu erzeugen. Beispielsweise könnte der Compiler die Vergleichsoperatoren und swap-Funktionen für einfache Klassen selbst generieren, wenn man ihn explizit dazu anweist.

Deleted Special Functions

In gewisser Weise sind die deleted Funktionen das Gegenteil davon. Mit ihnen kann sichergestellt werden, dass bestimmte Funkionen nicht aufgerufen werden. Die naheliegendste Anwendung dafür sind nicht kopierbare Klassen, für die bisher bestimmte Funktionen privat deklariert wurden.

class foobar { void* _handle; // directly managed pointer public: foobar(); ~foobar(); // NOTE: disable copy constructor and assignment operator foobar(const foobar&) = delete; foobar& operator=(const foobar&) & = delete; // ... further members };

Dieser Fall wurde bisher in der Regel mit Hilfe privater Funktionen adressiert. Mit der neuen Möglichkeit wird die Absicht des Entwicklers aber klarer, die Fehlermeldungen des Compilers besser und es werden auch Randfälle ausgeschlossen, wie beispielsweise der Versuch die Kopier-Operation innerhalb einer Member-Funktion durchzuführen.

Deleted Functions im Allgemeinen

Aus dem gerade geschriebenen könnte man annehmen, dass die =delete-Syntax dafür sorgt, dass spezielle Funktionen nicht automatisch generiert werden. Tatsächlich verhalten sich die deleted Funktionen aber anders. Am ehesten sind sie mit privaten Member-Funktionen vergleichbar, mit einigen wichtigen Unterschieden:

Genauso wie private Funktionen nehmen aber auch die deleted Funktionen an der Overload-Resolution teil. Damit kann man sie verwenden, um bestimmte Aufrufe zu vermeiden – insbesondere im Zusammenhang mit implizierten Konvertierungen.

// actual function void write(double value); // avoid invoking the function with an int argument void write(int value) = delete; int main() { write(42.0); // okay write(42); // ERROR: this line does not compile }

In diesem Beispiel wird verhindert, dass die Funktion write mit einem int-Argument aufgerufen wird. Damit das auch für alle anderen integralen Datentypen funktioniert, bietet es sich an, in diesem Fall ein template zu definieren. Dadurch wird die nicht-gelöschte Funktion nur dann verwendet, wenn die Parameter genau passen.

// actual function void write(double value); // avoid invoking the function with all other argument types template <typename Type> void write(Type value) = delete;

Hilfreich ist der Mechanismus auch im Zusammenhang mit L‑Value- und R‑Value-References. In einigen Fällen erwarten Funktionen das Argument als Const-Referenz, die sie über den Lebenszeit des Funktionsaufrufs hinweg speichern. In diesen Fall wäre es gefährlich, ein temporäres Objekt an diese Referenz zu binden. Mit Hilfe der gelöschten Funktionen und der R‑Value-Referenzen lässt sich das nun einfach erreichen.

struct object { }; struct holder { const object& _ref; explicit holder(const object& ref) : _ref(ref) { } // avoid invoking the constructor with a temporary holder(const object&&) = delete; };

Die deleted-Funktionen bieten damit Möglichkeiten, die ich bisher so nicht gekannt habe. Und es fällt mir aktuell schwer zu beurteilen, in welchen Fällen ich davon Gebrauch machen werde. In jedem Fall werde ich bei neuem Code auf einen sinnvollen Einsatz achten und beobachten, welchen Nutzen sie tatsächlich bringen.