C++ コア ガイドライン:C++20 のコントラクトへの短い回り道

私の当初の計画では、エラー処理の次のルールについてこの記事に書きました。しかし、私は将来について書く計画を変更しました:C++20 でのコントラクトです。

Fabuio 著 - 自作、CC0、リンク

スキップするルールは次のとおりです。

  • E.4:不変条件を中心にエラー処理戦略を設計する
  • E.5:コンストラクターに不変条件を確立させ、それができない場合はスローします
  • E.6:RAII を使用してリークを防止する
  • E.7:前提条件を述べる
  • <リ>

    E.8:事後条件を述べる

なぜプランを変更したのですか?いくつかの理由でそれを行いました。

  • C++ コア ガイドラインで引用されているエラー処理のルールには十分な内容がありません。
  • ルール E.6 については、投稿全体で既に書いています:ガベージ コレクション - ノー サンクス。もちろん、繰り返したくありません。
  • 5 つのルールのうち 4 つは、契約による設計に関するものです。

これらのポイントの結果は非常に単純です。コントラクトはエラー処理にとって重要なようです。C++20 にはコントラクトがあると思われます。そのため、C++20 のコントラクトについてこの記事に書きます。

契約の詳細を知りたい場合。この投稿は、提案 P0380R1 および P0542R5 に基づいています。

まず第一に。

契約とは

コントラクトは、ソフトウェア コンポーネントのインターフェイスを正確かつチェック可能な方法で指定します。これらのソフトウェア コンポーネントは通常、事前条件、事後条件、不変条件を満たさなければならない関数とメソッドです。以下は、提案からの短縮された定義です。

  • 前提条件 :関数のエントリ時に保持される述語。関数定義の外に配置されます。
  • 事後条件 :関数の終了時に保持される述語。関数定義の外に配置されます。
  • 主張 :計算のその時点で保持されるはずの述語。関数定義内に配置されます。

事前条件と事後条件は C++20 では関数定義の外側に配置されますが、不変条件は関数定義の内側に配置されます。述語はブール値を返す関数です。

これが最初の例です:
int push(queue& q, int val) 
 [[ expects: !q.full() ]]
 [[ ensures !q.empty() ]]{
 ...
 [[assert: q.is_ok() ]]
... }

属性 Expects は前提条件、属性 ensure は事後条件、属性 assert は表明です。

関数 push のコントラクトは、要素を追加する前にキューがいっぱいにならず、追加後に空にならず、アサーション q.is_ok() が保持されることです。

事前条件と事後条件は、関数インターフェイスの一部です。これは、関数のローカル メンバー、またはクラスのプライベートまたは保護されたメンバーにアクセスできないことを意味します。対照的に、アサーションは実装の一部であるため、クラスのプライベートまたは保護されたメンバーの関数のローカル メンバーにアクセスできます。

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

m は非公開であるため、前提条件の一部にすることはできません。

デフォルトでは、契約違反はプログラムを終了させます。これは完全な話ではありません。詳細を説明させてください。

詳細

コントラクト属性の完全な構文は次のとおりです。 [[contract-attribute modifier:conditional-expression ]]

  • 契約属性 :期待し、保証し、主張する
  • 修飾子: 契約レベルまたは契約の執行を指定します。可能な値は、default、audit、および axiom
      です。
    • default:実行時のチェックのコストは小さくなければなりません。これはデフォルトの修飾子です
    • 監査:実行時チェックのコストは大きいと想定されます
    • 公理:述語は実行時にチェックされません
  • 条件式 :コントラクトの述語

ensure 属性については、追加の識別子が利用可能です。 [[修飾子識別子を保証:条件式]]

識別子 関数の戻り値を参照できます。

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

識別子としての res は、この場合、任意の名前です。例に示すように、同じ種類のコントラクトをさらに使用できます。

モディファイヤと契約違反の処理について詳しく説明します。

契約違反の処理

コンパイルには 3 つのアサーション ビルド レベルがあります:

  • オフ: 契約はチェックされません
  • デフォルト: デフォルトの契約がチェックされます。これがデフォルトです
  • 監査: デフォルトと監査契約がチェックされます

契約違反が発生した場合 (つまり、述語が false と評価された場合)、違反ハンドラーが呼び出されます。違反ハンドラは、const std::contract_violation を取り、void を返す noexcept 型の関数です。関数が noexcept であるため、これは契約違反の場合に std::terminate が呼び出されることを意味します。ユーザーは違反ハンドラーを設定できます。

クラス std::contract_violation は、契約違反に関する情報を提供します。

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:契約違反の行番号
  • file_name:契約違反のファイル名
  • function_name:契約違反の関数名
  • comment:コントラクトの述語
  • assertion_level:コントラクトに対するアサーション レベル

コントラクトを宣言する場合、覚えておくべきルールがいくつかあります。

契約の宣言

関数の宣言にコントラクトを置くことができます。これには、仮想関数または関数テンプレートの宣言が含まれます。

  • 関数のコントラクト宣言は同一でなければなりません。最初の宣言とは異なる宣言では、コントラクトを省略できます。
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

  • オーバーライド関数でコントラクトを変更することはできません。

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
};

クラス D のコントラクト定義はどちらも誤りです。メソッド f のコントラクトは、B::f のコントラクトとは異なります。メソッド D::g はコントラクトを B::g に追加します。

まとめ

感動?私も!コントラクトが関数の書き方やインターフェイスや例外処理の考え方を根本的に変えることは、まだ想像できません。 Sutter's Mill に関する Herb Sutter の考えは、彼にとって「コントラクトはこれまでのところ C++20 の最も影響力のある機能であり、間違いなく C++11 以降に C++ に追加された最も影響力のある機能です ."

次は?

次回の投稿では、現在に戻り、例外処理のルールについて書きます。

詳細情報

わお!ほぼ 200 人の読者が、次の pdf バンドルの投票に参加しました。受賞者はこちらです。

  • ドイツ語 pdf バンドル:埋め込み:Performanz zählt
  • 英語の pdf バンドル:C++ コア ガイドライン:同時実行と並列処理
投票の詳細は次のとおりです:
  • ドイツのブログ:Welches PDF-Päckchen soll ich zusammenstellen?マヘ・デイン・クロイツ!
  • 英語のブログ:どの PDF バンドルを提供すればよいですか?選択してください!

PDF バンドルの校正と準備に少なくとも 1 週間は必要です