
2026/10/15 22:11
C++26 のコントラクト:前置条件と後置条件の紹介
RSS: https://news.ycombinator.com/rss
要約▶
Japanese Translation:
C++26 では、契約仕様が (
pre、post) および contract_assert 関数が導入され、コード内で直接仮定と保証を表現できるようになりました(機能 P2900)。事前条件(pre(...))はパラメータリストの後に配置され、入力の要件を定義するのに対し、事後条件(post(result: ...)) は通常の返還時の期待される状態を指定し、単一のアサーションで複数の返還経路をカバーできます。内部的な不変性は contract_assert を使用して関数体内でチェックされます。契約がヘッダーファイルに記述できるという点も重要な利点であり、実装詳細を確認せずとも API の利用者が仮定と保証を目に見えるようにします。本文
C++26 契約機能(Contracts)の活用と実装ガイド
C++26 において導入される「契約(Contracts)」機能について解説します。従来の
assert や入力検証との違い、宣言と定義における扱い方、そして GCC での利用方法をまとめました。
アサーションからの出発
標準的な C++ では、関数内部に有効なインデックスをチェックするアサーションを記述することが一般的でした。
従来の assert
の例
assert// foo.h #include <vector> int get_value(const std::vector<int>& values, std::size_t index);
// foo.cpp #include "foo.h" #include <cassert> int get_value(const std::vector<int>& values, std::size_t index) { assert(index < values.size()); // 条件違反時に診断メッセージを出力して終了 return values[index]; }
課題点:
マクロを定義するとチェックが無効化され、実質的にコードから消失します。NDEBUG- 要件が実装ファイルの内部に隠されており、API を読むだけでは条件関係が分かりません。
C++26 契約機能の概要
C++26 では、
assert の代わりに pre(...) や post(...) という構文を用いて関数自体に要件を記述できます。
前置条件と後置条件
int get_value(const std::vector<int>& values, std::size_t index) pre(index < values.size()) // 関数呼び出し時に満たされるべき条件 { return values[index]; }
- ヘッダファイルに宣言を書くだけで、実装を確認せずに要件が可視化されます。
に依存せず、デバッグ時だけでなくビルド時の挙動を制御できます。NDEBUG
契約機能の特徴と利点
なぜ assert
ではいけないのか?
assert| 特徴 | | 契約 (Contracts) |
|---|---|---|
| 記述場所 | 実装内部のみ | 宣言と定義の両方に配置可能 |
| 制御方法 | で OFF/OFF切り替え | 評価セマンティクスで動作を選択可能 |
| 概念 | 単なるエラーチェック | 入力・出力・内部の状態の区別 |
契約の評価セマンティクス
C++26 では以下の 4 つの実行時挙動(セマンティクス)の中から実装側が選択できます。
- Ignore (無視): 述語は評価されません。
- Observe (観察): 違反ハンドラーが呼ばれ、正常終了すればプログラムは続行されます。
- Enforce (強制): 違反ハンドラーが呼ばれ、正常終了すればプログラムは終了します。
- Quick-enforce (迅速強制): ハンドラーを呼び出すことなく即座に実行を終了します。
重要: C++26 の仕様では、個別の述語ごとにセマンティクスを指定する移植性の高い構文はまだ存在しません。実装側の設定で決定されます。
完全な実装例と挙動確認
インデックス越えチェック
#include <print> #include <vector> int get_value(const std::vector<int>& values, std::size_t index) pre(index < values.size()) // 宣言部分に記述 { return values[index]; }
コンパイルと実行(GCC)
-fcontracts フラグで有効化し、違反時の動作を確認できます。
g++ -std=c++26 -fcontracts \ -fcontract-evaluation-semantic=enforce example.cpp
出力結果:
- 正常呼び出し:
→get_value(values, 1)
を返す20 - 違反呼び出し (
): 例外処理なしでプログラムがindex=10
で終了します。SIGABRT
後置条件の実例
関数が戻った際の状態を保証する
post 述語も記述可能です。
int clamp_value(int value, const int low, const int high) pre(low <= high) // 境界が有効であることを保証 post(result: result >= low && result <= high) // 戻り値が境界内に含まれることを保証 { if (value < low) return low; if (value > high) return high; return value; }
注意点:後置条件内の
パラメータconst
- 後置条件で参照するパラメータは、すべての宣言において
でなければなりません。const - 例外で関数から退出した際のみ、後置条件のチェックはスキップされます。
GCC での契約機能利用
現在の GCC(16)のみがサポートしており、特定のフラグが必要です。
コンパイルオプション
g++ -std=c++26 -fcontracts \ -fcontract-evaluation-semantic=enforce example.cpp
カスタムハンドラーの登録
違反時のエラーメッセージをロギングするハンドラーを設定できます。
#include <contracts> #include <print> void handle_contract_violation( const std::contracts::contract_violation& violation) { std::println(stderr, "Contract violated: {}", violation.comment()); }
注意: ハンドラーで正常終了しても、
モードの場合はプログラムは終了します。enforce
関数内部のチェックとミューテーション
contract_assert
の使用
contract_assert関数の中間段階や戻り値の計算後に性質を保証するために使用します。
std::size_t count_spaces(std::string_view text) { std::size_t count = 0; for (char ch : text) { if (ch == ' ') ++count; } contract_assert(count <= text.size()); // 文字数より空白数は多いはず return count; }
述語内部のミューテーション回避
契約述語は自動的に
const として扱われるため、内部変数の変更が禁止されます。
void process(int count) pre(++count > 0) // エラー:count は read-only とみなされるためインクリメント不可 { }
- これは「述語そのものが副作用を全く持たない」という意味ではありません。グローバル状態の変更などは可能です。ただし、契約評価が無視されている可能性があるため、述語はプログラムの状態を変更してはいけません。
入力検証との違い
契約機能は入力検証の代替ではありません。
- 契約 (
,pre
): アルゴリズム的な性質や内部ヘルパーの要件を文書化します(実行時チェック)。post - 制御フロー (
,if
): アプリケーションレベルの入力エラー(無効なファイル読み込みなど)を処理し、エラーメッセージを表示して正常に終了させるべきです。return
重要: 契約アサーションは制御フローの一部ではなく、エラー処理や入力検証の手段として使用しないでください。
まとめ
C++26 の契約機能で実現できることを整理します。
: 関数呼び出し時に満たされるべき条件を宣言します。pre(...)
: 関数が正常に戻った際に満たされるべき条件を宣言します。post(...)
: 関数の内部特定の地点での性質を保証します。contract_assert(...)
重要なポイント:
- API の要件は、ヘッダファイルの最初の宣言で記述するのが自然です。
- 契約は常にチェックされるわけではないため(
モードなど)、ロジックや副作用を記述に依存してはいけません。ignore - 通常の入力検証やエラー処理には通常の制御フローを使用してください。