Softwareentwicklung: Contracts in C++26​

Contracts sind ein wichtiges Feature, das Bedingungen für Funktionen definiert und eigentlich schon in C++20 landen sollte.

In Pocket speichern vorlesen Druckansicht 20 Kommentare lesen

(Bild: Chokniti Khongchum/Shutterstock.com)

Lesezeit: 3 Min.
Von
  • Rainer Grimm
Inhaltsverzeichnis

Nach der Kurzserie zu Reflection in C++26 geht es nun um Contracts. Damit kann man Preconditions, Postconditions und Invariants für Funktionen angeben.

Modernes C++ – Rainer Grimm

Rainer Grimm ist seit vielen Jahren als Softwarearchitekt, Team- und Schulungsleiter tätig. Er schreibt gerne Artikel zu den Programmiersprachen C++, Python und Haskell, spricht aber auch gerne und häufig auf Fachkonferenzen. Auf seinem Blog Modernes C++ beschäftigt er sich intensiv mit seiner Leidenschaft C++.

Contracts sollten bereits Teil von C++20 sein, wurden aber in der Standardbesprechung in Köln entfernt. Herb Sutter sagte auf Sutter's Mill Folgendes darüber: „Contracts is the most impactful feature of C++20 so far, and arguably the most impactful feature we have added to C++ since C++11.” Mit C++26 bekommen wir sie wahrscheinlich.

Dieser Artikel basiert auf dem Proposal P2961R2.

Ein Contract legt Schnittstellen für Softwarekomponenten auf präzise und überprüfbare Weise fest. Diese Softwarekomponenten sind Funktionen und Methoden, die Precondition, Postconditions und Invariants erfüllen müssen. Hier sind die Definitionen:

  • Eine Precondition: ein Prädikat, das beim Eintritt in eine Funktion gelten soll,
  • Eine Postcondition: ein Prädikat, das beim Verlassen der Funktion gelten soll,
  • Eine Assertion: ein Prädikat, das an seinem Punkt in der Berechnung gelten soll.

Die Precondition und die Postcondition werden außerhalb der Funktionsdefinition platziert, die Invariante jedoch innerhalb der Funktionsdefinition. Ein Prädikat ist ein Ausdruck, der einen Booleschen Wert zurückgibt.

Bevor ich euch das erste Beispiel zeige, möchte ich etwas über die Ziele des Vertragsdesigns schreiben.

Im Englischen sind folgende Designziele definiert:

  • The syntax should fit naturally into existing C++. The intent should be intuitively understandable by users unfamiliar with contract checks without creating any confusion.
  • A contract check should not resemble an attribute, a lambda, or any other pre-existing C++ construct. It should sit in its own, instantly recognisable design space.
  • The syntax should feel elegant and lightweight. It should not use more tokens and character than necessary.
  • To aid readability, the syntax should visually separate the different syntactic parts of a contract check. It should be possible to distinguish at a glance the contract kind, the predicate, the name for the return value … (Proposal P2961R2)

Nun folgt das erste Beispiel:

int f(int i)
    pre (i >= 0)
    post (r: r > 0)
{
    contract_assert (i >= 0);
    return i+1;
}

pre oder post

  • fügen eine Precondition beziehungsweise Postcondition hinzu. Eine Funktion kann eine beliebige Anzahl von Preconditions und Postconditions haben. Sie können beliebig miteinander vermischt werden.
  • sind kontextabhängige Schlüsselwörter, also in bestimmten Kontexten ein Schlüsselwort, aber ein Bezeichner außerhalb dieses Kontextes.
  • sind am Ende der Funktionsdeklaration positioniert.

post kann einen Rückgabewert haben. Der Bezeichner muss vor dem Prädikat stehen, gefolgt von einem Doppelpunkt.

contract_assert

  • ist ein Schlüsselwort. Andernfalls könnte es nicht von einem Funktionsaufruf unterschieden werden.

Das ideale Schlüsselwort für die Zusicherung wäre assert, aber nicht contract_assert. assert wird in den meisten Programmiersprachen verwendet, um vertragsähnliche Behauptungen auszudrücken. Aber C++ hat ein Legacy-Problem.

#include <cassert>

void f() {
    int i = get_i();
    assert(i >= 0); // identical syntax for contract assert and macro assert!
    use_i(i);
}

assert ist bereits ein Makro aus dem Header <cassert>.

Wird der Contract verletzt, führt dies zu einem Laufzeitfehler.

// contract.cpp

#include <iostream>

int f(int i)
    pre (i >= 0)
    post (r: r > 0)
{
    contract_assert (i >= 0);
    return i+1;
}

int main() {

    std::cout << '\n';    
    
    f(-1);
    
    std::cout << '\n';
    
}

In meinem nächsten Artikel werde ich mich mit den kleineren Features der C++26-Kernsprache befassen. (rme)