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.

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.

Wie der Name schon sagt, ignoriert die ignore-Semantik die Auswertung des Prädikats. Dennoch muss das Prädikat syntaktisch korrekt sein.

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.

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.

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.

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 einen std::source_location zurück, der den Ort der Vertragsverletzung angibt.

Weitere Informationen zum std::contracts::contract_violation-Objekt finden sich auf der cppreference-Seite.

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)



Source link

Beliebt

Die mobile Version verlassen