C++ Core Guidelines:Ein kurzer Abstecher zu Verträgen in C++20

C++ Core Guidelines:Ein kurzer Abstecher zu Verträgen in C++20

Mein ursprünglicher Plan war es, in diesem Beitrag über die nächsten Regeln zur Fehlerbehandlung zu schreiben. Aber ich habe meinen Plan geändert, über die Zukunft zu schreiben:Verträge in C++20.

Von Fabuio - Eigene Arbeit, CC0, Link

Hier sind die Regeln, die ich überspringe.

  • E.4:Gestalten Sie Ihre Fehlerbehandlungsstrategie um Invarianten herum
  • E.5:Lassen Sie einen Konstruktor eine Invariante erstellen und werfen Sie, wenn dies nicht möglich ist
  • E.6:Verwenden Sie RAII, um Lecks zu verhindern
  • E.7:Nennen Sie Ihre Voraussetzungen
  • E.8:Geben Sie Ihre Nachbedingungen an

Warum habe ich meinen Plan geändert? Ich habe es aus mehreren Gründen getan.

  • Die zitierten Regeln zur Fehlerbehandlung in den C++-Kernrichtlinien haben nicht genug Fleisch.
  • Über Regel E.6 habe ich bereits in einem ganzen Post geschrieben:Garbage Collection - No Thanks. Ich möchte mich natürlich nicht wiederholen.
  • Vier der fünf Regeln beziehen sich auf Design by Contract.

Die Konsequenz dieser Punkte ist ganz einfach. Verträge scheinen für die Fehlerbehandlung wichtig zu sein, C++20 wird vermutlich Verträge haben, daher schreibe ich in diesem Beitrag über Verträge in C++20.

Falls Sie mehr Details zu Verträgen haben möchten. Dieser Beitrag basiert auf den Vorschlägen P0380R1 und P0542R5.

Zunächst einmal.

Was ist ein Vertrag?

Ein Vertrag legt Schnittstellen für Softwarekomponenten präzise und überprüfbar fest. Diese Softwarekomponenten sind typischerweise Funktionen und Methoden, die Vorbedingungen, Nachbedingungen und Invarianten erfüllen müssen. Hier sind die verkürzten Definitionen aus den Vorschlägen.

  • Eine Voraussetzung :ein Prädikat, das beim Eintritt in eine Funktion gelten soll. Es wird außerhalb der Funktionsdefinition platziert.
  • Eine Nachbedingung :ein Prädikat, das beim Verlassen der Funktion gelten soll. Es wird außerhalb der Funktionsdefinition platziert.
  • Eine Behauptung :ein Prädikat, das an seinem Punkt in der Berechnung gelten soll. Es wird innerhalb der Funktionsdefinition platziert.

Die Vorbedingung und die Nachbedingung werden in C++20 außerhalb der Funktionsdefinition platziert, aber die Invariante wird innerhalb der Funktionsdefinition platziert. Ein Prädikat ist eine Funktion, die einen booleschen Wert zurückgibt.

Hier ein erstes Beispiel:
int push(queue& q, int val) 
 [[ expects: !q.full() ]]
 [[ ensures !q.empty() ]]{
 ...
 [[assert: q.is_ok() ]]
... }

Das Attribut „expect“ ist eine Vorbedingung, das Attribut „secure“ ist eine Nachbedingung und das Attribut „assert“ ist eine Behauptung.

Die Verträge für die Funktion push sind, dass die Warteschlange vor dem Hinzufügen eines Elements nicht voll ist, dass sie nach dem Hinzufügen nicht leer ist und die Behauptung q.is_ok() gilt.

Vor- und Nachbedingungen sind Teil der Funktionsschnittstelle. Das bedeutet, dass sie nicht auf lokale Member einer Funktion oder private oder geschützte Member einer Klasse zugreifen können. Im Gegensatz dazu sind Zusicherungen Teil der Implementierung und können daher auf lokale Mitglieder einer Funktion von privaten oder geschützten Mitgliedern einer Klasse zugreifen.

class X {
public:
 void f(int n)
 [[ expects: n<m ]] // error; m is private
 {
 [[ assert: n<m ]]; // OK
 // ...
 }
private:
 int m;
}; 

m ist privat und kann daher nicht Teil einer Vorbedingung sein.

Standardmäßig beendet eine Vertragsverletzung das Programm. Dies ist nicht die ganze Geschichte, lassen Sie mich Ihnen mehr Details geben.

Weitere Details

Hier ist die vollständige Syntax der Vertragsattribute: [[Vertragsattribut-Modifikator:Bedingungsausdruck ]]

  • Vertragsattribut :erwartet, gewährleistet und behauptet
  • Modifizierer: legt die Vertragsebene oder die Durchsetzung des Vertrags fest; mögliche Werte sind default, audit und axiom
    • Standard:Die Kosten für die Laufzeitprüfung sollten gering sein; es ist der Standardmodifikator
    • Audit:Es wird angenommen, dass die Kosten für die Laufzeitprüfung hoch sind
    • Axiom:Das Prädikat wird zur Laufzeit nicht geprüft
  • bedingter Ausdruck :das Prädikat des Vertrages

Für das Attribut „Gewährleistet“ steht ein zusätzlicher Bezeichner zur Verfügung. [[stellt Modifikator-ID sicher:Bedingungsausdruck ]]

Die Kennung lassen Sie sich auf den Rückgabewert der Funktion beziehen.

int mul(int x, int y)
 [[expects: x > 0]] // implicit default
 [[expects default: y > 0]]
 [[ensures audit res: res > 0]]{
 return x * y;
}

res als Bezeichner ist in diesem Fall ein beliebiger Name. Wie im Beispiel gezeigt, können Sie mehrere Verträge der gleichen Art verwenden.

Lassen Sie mich näher auf die Modifikatoren und den Umgang mit Vertragsverletzungen eingehen.

Umgang mit Vertragsverletzungen

Eine Kompilierung hat drei Assertion-Build-Levels:

  • aus: es werden keine Verträge geprüft
  • Standard: Ausfallverträge werden geprüft; dies ist die Vorgabe
  • Audit: Ausfall und Revisionsvertrag werden geprüft

Wenn eine Vertragsverletzung auftritt – das heißt, das Prädikat wird als falsch ausgewertet –, wird der Verletzungsbehandler aufgerufen. Der Violation Handler ist eine Funktion vom Typ noexcept, die eine Konstante std::contract_violation nimmt und einen void zurückgibt. Da die Funktion noexcept ist, bedeutet dies, dass std::terminate im Falle einer Vertragsverletzung aufgerufen wird. Ein Benutzer kann einen Handler für Verstöße festlegen.

Die Klasse std::contract_violation gibt Auskunft über die Vertragsverletzung.

namespace std{ 
 class contract_violation{
 public:
 uint_least32_t line_number() const noexcept;
 string_view file_name() const noexcept;
 string_view function_name() const noexcept;
 string_view comment() const noexcept;
 string_view assertion_level() const noexcept;
 };
}

  • line_number:Zeilennummer der Vertragsverletzung
  • file_name:Dateiname der Vertragsverletzung
  • function_name:Funktionsname der Vertragsverletzung
  • Kommentar:Prädikat zum Vertrag
  • assertion_level:Assertion Level zum Vertrag

Es gibt ein paar Regeln, die Sie beachten sollten, wenn Sie einen Vertrag abschließen.

Vertragserklärung

Auf die Deklaration einer Funktion kann ein Vertrag geschlossen werden. Dazu gehören Deklarationen virtueller Funktionen oder Funktionsvorlagen.

  • Die Contracts-Deklaration einer Funktion muss identisch sein. Jede von der ersten abweichende Erklärung kann den Vertrag hinfällig machen.
int f(int x) 
 [[expects: x>0]]
 [[ensures r: r>0]];

int f(int x); // OK. No contract.

int f(int x)
 [[expects: x>=0]]; // Error missing ensures and different expects condition

  • Ein Vertrag kann nicht in übergeordneter Funktion geändert werden.

struct B{
 virtual void f(int x)[[expects: x > 0]];
 virtual void g(int x);
}

struct D: B{
 void f(int x)[[expects: x >= 0]]; // error
 void g(int x)[[expects: x != 0]]; // error
};

Beide Vertragsdefinitionen der Klasse D sind fehlerhaft. Der Kontrakt der Methode f unterscheidet sich von dem aus B::f. Die Methode D::g fügt B::g einen Vertrag hinzu.

Abschlussgedanken

Beeindruckt? Ich auch! Ich kann mir immer noch nicht vorstellen, wie grundlegend Verträge die Art und Weise verändern werden, wie wir Funktionen schreiben und über Schnittstellen und Ausnahmebehandlung nachdenken. Vielleicht geben Ihnen die Gedanken von Herb Sutter zu Sutter's Mill eine Vorstellung, denn für ihn sind „Contracts das bisher wirkungsvollste Feature von C++20 und wohl das wirkungsvollste Feature, das wir seit C++11 zu C++ hinzugefügt haben ."

Was kommt als nächstes?

Mit meinem nächsten Beitrag werde ich mit einem Schritt zurück in die Gegenwart fortfahren und über die Regeln zur Ausnahmebehandlung schreiben.

Weitere Informationen

Wow! Knapp 200 Leser haben an der Abstimmung für das nächste PDF-Bundle teilgenommen. Hier sind die Gewinner.

  • Deutsches PDF-Bundle:Eingebettet:Performanz zählt
  • Englisches PDF-Bundle:C++ Core Guidelines:Concurrency and Parallelism
Hier sind die Details der Abstimmung:
  • Deutscher Blog:Welches PDF-Päckchen soll ich zusammenstellen? Mache dein Kreuz!
  • Deutscher Blog:Welches PDF-Bundle soll ich bereitstellen? Treffen Sie Ihre Wahl!

Ich brauche mindestens eine Woche, um die PDF-Pakete Korrektur zu lesen und vorzubereiten