Künstliche Intelligenz
Contracts in C++26: Evaluations-Semantik | heise online
Nachdem ich in meinem letzten Artikel Contracts in C++26: Ein tiefer Einblick in die Verträge kurz die Details von Contracts vorgestellt habe, möchte ich mich nun näher mit der sehr interessanten Evaluations-Semantik befassen.
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++.
Wenn eine Contracts Assertion auftritt, wird eine der vier Bewertungssemantiken angewendet: ignore
, observe
, enforce
und quick-enforce
. Die Tabelle gibt einen Überblick über die vier Semantiken:
(Bild: Rainer Grimm)
Leider ist es derzeit nicht möglich, diese Evaluations-Semantiken in vollem Umfang zu sehen.
Checking-Semantik und Terminating-Semantik
Während die Checking-Semantik die Vertrags-Assertion nur auswertet, beendet die Terminating-Semantik auch das Programm.
Eine standardkonforme Implementierung muss nicht jede der vier genannten Evaluations-Semantiken implementieren. Sie kann auch ihre eigene Semantik anbieten.
ignore
Wie der Name schon sagt, ignoriert die ignore
-Semantik die Auswertung des Prädikats. Dennoch muss das Prädikat syntaktisch korrekt sein.
observe
Die observe
-Semantik ist eine Checking-Semantik. Im Allgemeinen können drei Bedingungen zu einer Vertragsverletzung führen:
- Die Auswertung des Prädikats gibt
false
zurück. - Die Auswertung des Prädikats verursacht eine Exception.
- Die Auswertung des Prädikats erfolgt zur Compile-Zeit, aber das Prädikat ist kein konstanter Ausdruck.
Wenn zur Compile-Zeit eine Vertragsverletzung auftritt, wird eine Diagnose ausgegeben und die Kompilierung fortgesetzt.
Wenn eine Vertragsverletzung zur Laufzeit auftritt, wird der Vertragsverletzungs-Handler aufgerufen, der auf ein Objekt vom Datentyp const std::contracts::contract_violation
verweist, das Informationen über die Vertragsverletzung enthält. Wenn der Vertragsverletzungs-Handler normal zurückkehrt, wird die Programmausführung fortgesetzt.
enforce
Die enforce
-Semantik ruft den Vertragsverletzungs-Handler zur Laufzeit auf. Das Programm wird beendet, wenn der Contract-Violation-Handler normal zurückkehrt. Die enforce
-Semantik ist eine sogenannte terminierende Semantik. Das bedeutet, dass im Falle einer Vertragsverletzung die Programmausführung beendet wird. Danach kann eine der folgenden Aktionen erfolgen:
- Aufruf von
std::terminate
, - Aufruf von
std::abort
oder - sofortige Beendigung der Ausführung.
Zur Compile-Zeit wird die Kompilierung abgebrochen.
quick-enforce
Die quick-enforce
-Semantik ruft den Vertragsverletzungs-Handler zur Laufzeit nicht auf. Als terminierende Semantik beendet sie das Programm sofort. In diesem Fall wird beispielsweise __builtin_trap()
verwendet.
Zur Compile-Zeit wird die Kompilierung abgebrochen.
Vertragsverletzungs-Handler
Der Vertragsverletzungs-Handler hat folgende Signatur:
void handle_contract_violation( std::contracts::contract_violation );
Die Implementierung stellt den Standard-Vertragsverletzungs-Handler bereit. Sie kann jedoch auch zulassen, dass dieser Standard-Vertragsverletzungs-Handler durch einen benutzerdefinierten ersetzt wird.
In seiner ausgezeichneten Präsentation Contracts for C++ auf der ACCU 2025 stellt Timur Doumler einige spannende Beispiele für benutzerdefinierte Vertragsverletzungshandler vor:
// Protokollierung
void handle_contract_violation( std::contracts::contract_violation violation ) {
LOG(std::format("Contract violated at: {}\n", violation.location()));
}
// Setze einen Haltepunkt
void handle_contract_violation( std::contracts::contract_violation violation ) {
std::breakpoint();
}
// Warte, bis ein Debugger angeschlossen ist
void handle_contract_violation( std::contracts::contract_violation violation ) {
while (!std::is_debugger_present())
/* spin */
std::breakpoint();
}
// Stacktrace ausgeben
void handle_contract_violation( std::contracts::contract_violation violation ) {
std::cout << std::stacktrace::current(1);
}
// An den Standard-Handler für Vertragsverletzungen übergeben
void handle_contract_violation( std::contracts::contract_violation violation ) {
std::cout << std::stacktrace::current(1);
std::contracts::invoke_default_contract_violation_handler(violation);
}
Abschließend möchte ich die Schnittstelle des Objekts std::contracts::contract_violation
vorstellen, das der Vertragsverletzungs-Handler empfängt:
kind
gibt die Art der Vertragsverletzung zurück.semantic
gibt die Evaluations-Semantik zurück, wenn die Vertragsverletzung auftritt.is_terminating
gibt zurück, ob die Evaluations-Semantik beendet wird.detection_mode
gibt den Grund für die Vertragsverletzung zurück.evaluation_exception
gibt einen std::exception_ptrzur zur Ausnahme zurück, die bei der Prädikatbewertung ausgelöst wurde.comment
gibt eine erklärende Zeichenfolge zur Vertragsverletzung zurück.location
gibt einenstd::source_location
zurück, der den Ort der Vertragsverletzung angibt.
Weitere Informationen zum std::contracts::contract_violation
-Objekt finden sich auf der cppreference-Seite.
Wie geht es weiter?
In meinem nächsten Artikel werde ich mich auf die kleineren Features in C++26 konzentrieren. Ich beginne mit den kleinen Sicherheits-Features in der Kernsprache.
(rme)