C++ und der Unspecified-Bool-Type
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.