Blog 3 Min. Lesezeit

Defensive Programmierung mit neuen C++-Standards

Diesen Artikel teilen
Defensive programming with new C++ standards

Defensive Programmierung ist eine Form des defensiven Softwaredesigns, die sicherstellen soll, dass eine Software auch unter unvorhergesehenen Umständen weiterhin funktioniert. Defensive Programmiertechniken kommen häufig dort zum Einsatz, wo hohe Verfügbarkeit, Zuverlässigkeit oder Sicherheit erforderlich sind.

Defensive Programmierung ist ein Ansatz, um Software und Quellcode in folgenden Bereichen zu verbessern:

  • Allgemeine Qualität – die Anzahl von Softwarefehlern und Problemen reduzieren.
  • Verständlichkeit des Quellcodes – der Code sollte lesbar und nachvollziehbar sein, damit er einer Codeprüfung standhält.
  • Vorhersehbares Verhalten der Software auch bei unerwarteten Eingaben oder Benutzeraktionen.

Der klassische Weg, defensive Programmierung in C++ umzusetzen, ist die Verwendung von Assertions mit assert:

    void test( int *p ) {
        assert( p != 0 );
        if (p == 0)
            return;
        // use p.
    }

Eine Assertion ist Code, der während der Entwicklung eingesetzt wird – meist in Form einer Routine oder eines Makros – und es einem Programm ermöglicht, sich während der Ausführung selbst zu überprüfen. Ist die Assertion wahr, funktioniert alles wie erwartet. Ist sie falsch, wurde ein unerwarteter Fehler im Code erkannt.

Assertions sind besonders bei großen, komplexen Programmen sowie bei Software mit hohen Zuverlässigkeitsanforderungen nützlich. Sie helfen Entwicklern, widersprüchliche Annahmen an Schnittstellen, durch Codeänderungen eingeschleppte Fehler und ähnliche Probleme schneller aufzuspüren.

Eine Assertion verwendet üblicherweise zwei Angaben: einen booleschen Ausdruck, der die erwartete Bedingung beschreibt, und eine Meldung, die ausgegeben wird, wenn diese Bedingung nicht erfüllt ist.

Welche Mechanismen bieten die neuen C++-Standards für defensive Programmierung?

C++11 und static_assert

C++11 führt mit dem neuen Schlüsselwort static_assert eine Möglichkeit ein, Assertions bereits zur Kompilierzeit zu prüfen. Diese Funktion ist besonders nützlich, um Bedingungen für Template-Parameter festzulegen, wie die folgende Template-Klasse aus dem Folly-Quellcode zeigt:

c3

C++17 und das Attribut [[nodiscard]]

Functions declared with [[nodiscard]] gekennzeichnete Funktionen sollten vom Aufrufer nicht so verwendet werden, dass ihr Rückgabewert ignoriert wird. Das ist hilfreich, wenn Sie sicherstellen möchten, dass ein Rückgabewert geprüft wird: Der Vertrag einer Funktion lässt sich damit so ausdrücken, dass der Aufrufer den zurückgegebenen Wert nicht einfach übergeht.

Wird beispielsweise der Rückgabewert der Funktion do_something nicht verwendet, gibt der Compiler eine Warnung aus.

[[nodiscard]] error do_something (thing&);


do_something(my_thing); // Warning: ignored return value

C++20 und Contracts

Dies stellt eine bedeutende Verbesserung für defensive Programmierung in den neuen Standards dar: Es soll zahlreiche Mechanismen und Funktionen für Design by Contract bereitstellen.

Den entsprechenden Vorschlag finden Sie hier.

Sehen wir uns zunächst die Terminologie an, die im Vorschlag für Contracts verwendet wird:

1. A precondition is a predicate that should hold upon entry into a function. It expresses a function's expectation on its arguments and/or the state of objects that may be used by the function. Preconditions are expressed by expectsattributes (7.6.10).

2. A postcondition is a predicate that should hold upon exit from a function. It expresses the conditions that a function should ensure for the return value and/or the state of objects that may be used by the function. Postconditions are expressed by ensures attributes (7.6.11).

3. An assertion is a predicate that should hold at its point in a function body. It expresses the conditions, on objects that accessible at its point in a body, that must be satisfied. Assertions are expressed by assert attributes (7.6.12).

4. Preconditions, postoconditions, and assertions are collectively called contracts. A contract shall have no observable effect in a correct program (a program where all contracts would be satisified, if they were evaluated).

5. Contract attributes are followed by a conditional-expression, which is a potentially evaluated expression (3.2). 

Und hier ein Beispiel dafür, wie diese neue Funktion verwendet werden kann:


void push(int x, queue & q)
  [[expects: !q.full()]]
  [[ensures: !q.empty()]]
{
  //...
  [[assert: q.is_valid()]];
  //...
}

Der C++20-Standard sollte damit Funktionen bereitstellen, die Entwicklern bei defensiver Programmierung bislang gefehlt haben, und die Notwendigkeit externer Bibliotheken oder Frameworks für Design by Contract verringern.

Diesen Artikel teilen