Stroustrup argumentiert, dass Methoden (die Operationen, die die Attribute der Klasse verändern) diese Invarianten berücksichtigen müssen. Sind Invarianten für das Schreiben einer Methode irrelevant, so handelt es sich vermutlich um fragwürdiges Design, und Stroustrup empfiehlt, die Methode außerhalb der Klasse, als separate Operation, zu definieren. Die Invarianten sind für ihn generell die Motivation, Klassen zu definieren.
Um die Zuverlässigkeit von solchen Klassen zu erhöhen (auch unter Berücksichtigung von Bitkippern oder fehlgeleiteten Zeigern), muss man nichts weiter tun, als diese Invarianten durch Zusicherungen zu prüfen.
Zusicherungen in der objektorientierten Programmierung
Nicht nur die „Software-Prominenz“ jenseits des Atlantischen Ozeans rät zu Einsatz von Zusicherungen. Peter Liggesmeyer empfiehlt in seinem Buch „Software-Qualität“, Zusicherungen jedenfalls dann einzusetzen, wenn eine Klasse A von einer Klasse B erbt, aber A eine Methode von B überschreibt und bei dieser Methode eine neue Vorbedingung hinzukommt oder die Vorbedingung verfeinert wird [3]. Unter „Vorbedingung“ ist eine Annahme zu verstehen, die zutreffen muss, wenn die Methode oder Funktion aufgerufen wird. Also genau jene Dinge, die man durch Zusicherungen am Anfang einer Funktion überprüfen sollte, wie in Listing 3 gezeigt.
Die Verwendung von Zusicherungen kostet in vielen Fällen wenig Zeit, kann aber viel Zeit sparen. Nämlich dann, wenn man durch eine verletzte Zusicherung beim Debugging nicht vom Fehlersymptom aus auf die Fehlersuche geht, sondern von einem Fehlerzustand wegstarten kann, der selbst möglicherweise noch ohne erkennbare Symptome ist.
Zusicherungen zur Compile-Zeit
Die beschriebenen Formen der Zusicherungen beanspruchen Laufzeit und Speicher. Zudem wird ihr Wahrheitsgehalt nur überprüft, wenn die Zusicherung auch tatsächlich im Zuge des Programmlaufs ausgeführt wird. Es gibt aber ein paar Konsistenz-Prüfungen, die man schon zur Compile-Zeit machen kann und die daher weder Laufzeit noch Speicher kosten. Zudem sind diese Compile-Time-Assertions nicht darauf angewiesen, dass die Zusicherung tatsächlich ausgeführt wird.
Ein Beispiel für so eine Zusicherung ist die Überprüfung der Größen von Datentypen – etwa an Datenschnittstellen, bei der Koppelung von heterogenen Software-Teilen. Die folgenden Zeilen zeigen die Verwendung einer solchen Zusicherung. Es wird sichergestellt, dass die beiden Datentypen der memcpy-Operation die gleiche Größe haben:
memcpy(&operational_buf, &default_val, sizeof(default_values));
CT_ASSERT(sizeof(default_val) \ == sizeof(operational_buf));
Im Gegensatz zu regulären Zusicherungen ist es hier völlig egal, ob die Compile-Time-Zusicherung CT_ASSERT vor oder nach dem Kopiervorgang steht. Zur Laufzeit wird diese Zusicherung nicht ausgeführt. Der Code lässt sich gar nicht übersetzen, wenn die Zusicherung nicht wahr ist. Soweit zur Spezifikation, aber wie implementiert man nun CT_ASSERT?
Eine relative einfache Möglichkeit, den Übersetzungsvorgang bei falschem Wahrheitsgehalt der Compile-Zeit-Zusicherung zu stoppen, ist das Absetzen einer illegalen Deklaration. Beispiel:
extern char dummy[-1];
Die negative Feldgröße wird den Übersetzungsvorgang anhalten. Im Beispiel allerdings noch ohne Bedingung. Diese Bedingung ist in den folgenden Zeilen implementiert. Nur wenn der Wert des Parameters von CT_ASSERT Null ist (also die Zusicherung fehlschlägt), dann wird ein Feld negativer Größe deklariert und der Übersetzungsvorgang angehalten.
#define CT_ASSERT(exp) \ {extern char \ dummy[(exp) ? 1 : -1];}