C++ und der Unspecified-Bool-Type

von Hubert Schmid vom 2012-08-12

Beim Unspecified-Bool-Type handelt es sich um eines der Implementierungsmuster für C++, die auf Schwächen der Programmiersprache zurückzuführen sind. Dabei sollte doch alles so einfach sein: Die Aufgabe besteht lediglich darin, einen benutzerdefinierten Typ in einem bool'schen Kontext verwenden zu können – so wie es mit eingebauten Datentypen auch möglich ist.

Der Begriff unspecified-bool-type taucht beispielsweise in der Dokumentation der boost-Bibliothek und in der tr1-Spezifikation auf. Ein Vorzeigebeispiel ist sicherlich der shared_ptr aus beiden Dokumenten. Daran lässt sich die Situation einfach darstellen:

customer* c = find_customer_by_id(42); if (c) { // ... }

Sowohl in C als auch in C++ ist dieser Code zulässig. In der if-Bedingung wird geprüft, ob der Zeiger ungleich dem Nullzeiger ist. Nur in diesem Fall wird der Rumpf der Bedingungsanweisung ausgeführt.

Das Ziel ist, dieses Idiom mit einem shared_ptr<customer> genauso wie mit einem customer* verwenden zu können. C++ unterstützt das und ermöglicht, Typkonvertierungen für eigene Typen zu definieren. Das ist im folgenden Ausschnitt skizziert:

template <typename Type> class shared_ptr { Type* _ptr; operator bool() const { return _ptr != nullptr; } };

Damit funktioniert der Code im obigen Beispiel wie gewünscht. Allerdings bringt diese Typkonvertierung auch eine Reihe von Problemen mit sich, die vor allem darauf zurückzuführen sind, dass der Code in anderen Fällen damit unerwartete Dinge tut. Veranschaulichen möchte ich das durch folgendes Beispiel:

shared_ptr<int> p{new int{40}}; int answer = p* + 2;

Mit obiger Typkonvertierung übersetzt dieser Code ohne Warnung und speichert in der Variablen answer den Wert 2 – richtig gelesen – 2 – und nicht 42. Letzteres wäre das Ergebnis von *p + 2, doch hier stand p * +2 (mit verschobenem Leerzeichen). Dass dieser Code überhaupt übersetzt, liegt an dem Konvertierungsoperator. Denn damit kann der shared_ptr in einen bool konvertiert werden, der in der Multiplikation zu einer 1 wird.

Um diese Fehleranfälligkeit zu reduzieren, wird das Unspecified-Bool-Type-Pattern eingesetzt. Das funktioniert grob wie folgt: Statt einer Konvertierung nach bool wird eine Konvertierung in einen Zeigertyp definiert, die in den gewünschten Fällen einen vergleichbaren Effekt erzeugt. Dabei verwendet man am Besten einen Zeiger auf eine Member-Funktion, der keine Zeigerarithmetik zulässt.

Ich will gar nicht weiter darauf eingehen. Denn mit C++11 ist dieses Pattern überflüssig geworden. Mittlerweile kann man Konvertierungsoperatoren auch mit explicit auszeichnen, um die implizite und unerwünschte Konvertierung zu vermeiden. Die Verwendung innerhalb der if-Bedingung funktioniert hingegen nach wie vor. Im Beispiel sieht das wie folgt aus:

template <typename Type> class shared_ptr { Type* _ptr; explicit operator bool() const { return _ptr != nullptr; } };

Diese Erweiterung ist bereits in der Standardbibliothek von C++ sichtbar. Eine ganze Reihe von Typen verwenden diese neue Form für die Konvertierung in einen bool. Darunter auch std::shared_ptr und std::unique_ptr. Damit gehört das Unspecified-Bool-Type-Pattern hoffentlich bald der Vergangenheit an.