|
| 1 | +# 契約プログラミング [P2900R14] |
| 2 | +* cpp26[meta cpp] |
| 3 | + |
| 4 | +<!-- start lang caution --> |
| 5 | + |
| 6 | +このページはC++26に採用される見込みの言語機能の変更を解説しています。 |
| 7 | + |
| 8 | +のちのC++規格でさらに変更される場合があるため[関連項目](#relative-page)を参照してください。 |
| 9 | + |
| 10 | +<!-- last lang caution --> |
| 11 | + |
| 12 | +## 概要 |
| 13 | +C++26では、関数の正確な動作を明示的に指定でき、プログラムの正当性を高めるために「契約プログラミング」機能が導入される。 |
| 14 | + |
| 15 | +これにより、事前条件(preconditions)、事後条件(postconditions)、及びアサーション(assertions)をコード内で明示的に記述できるようになる。 |
| 16 | + |
| 17 | +この機能は、関数のインターフェースに対する期待値を明確にする役割があり、バグの早期発見、コードの可読性向上に寄与することが期待されている。 |
| 18 | + |
| 19 | +## 仕様 |
| 20 | +### 契約の種類 |
| 21 | +契約には以下の3種類が定められている。 |
| 22 | + |
| 23 | +- 事前条件(preconditions) |
| 24 | +- 事後条件(postconditions) |
| 25 | +- アサーション(assertions) |
| 26 | + |
| 27 | +#### 事前条件(pre) |
| 28 | +関数が呼び出される前に満たされているべき条件を指定する。 |
| 29 | +```cpp |
| 30 | +int safe_division(int numerator, int denominator) |
| 31 | + pre(denominator != 0) |
| 32 | +{ |
| 33 | + return numerator / denominator; |
| 34 | +} |
| 35 | +``` |
| 36 | +ここでは、`denominator`が0でないことを事前条件として指定している。 |
| 37 | +
|
| 38 | +#### 事後条件(post) |
| 39 | +関数の実行後に満たされているべき条件を指定する。 |
| 40 | +```cpp |
| 41 | +int increment(int x) |
| 42 | + post(r: r == x + 1) |
| 43 | +{ |
| 44 | + return x + 1; |
| 45 | +} |
| 46 | +``` |
| 47 | +ここでは、`increment`関数の戻り値が`x + 1`であることを事後条件として指定している。 |
| 48 | + |
| 49 | +`post`では、返り値を`r`としてバインドし、条件式内で利用している。ここには、任意の変数名が使用できる。変数は定数(`const`)な左辺値参照である。 |
| 50 | + |
| 51 | +#### アサーション(assert) |
| 52 | +関数の実行中に満たされているべき条件を指定する。 |
| 53 | +```cpp |
| 54 | +void return_negative(int value) |
| 55 | +{ |
| 56 | + contract_assert(value >= 0); |
| 57 | + return -value; |
| 58 | +} |
| 59 | +``` |
| 60 | +ここでは、`return_negative`関数のが引数として受け取っている`value`が0以上であることをアサーションとして指定している。 |
| 61 | +
|
| 62 | +`contract_assert`は、関数の本体内で使用される。 |
| 63 | +
|
| 64 | +また、これらの全ては、`[[ likely ]]`や`[[ unlikely ]]`、 `[[ maybe_unused ]]`属性を使用することができる。 |
| 65 | +```cpp |
| 66 | +void return_negative(int value) |
| 67 | + pre [[likely]] (value >= 0) |
| 68 | + post (r [[maybe_unused]] : r <= 0) |
| 69 | + { |
| 70 | + return -value; |
| 71 | + } |
| 72 | +``` |
| 73 | +### 評価の順番 |
| 74 | +契約式の評価順序に注意が必要である。 |
| 75 | + |
| 76 | +事前条件は関数の引数が初期化された後に評価され、事後条件は関数の戻り値が初期化された後、ローカル変数の破棄前に評価される。この順序により、契約式は必要な変数や状態にアクセスできるようになっている。 |
| 77 | + |
| 78 | +### 契約の評価モード |
| 79 | +契約の評価には、次の4つのモードが存在する。 |
| 80 | + |
| 81 | +- ignore: 契約のチェックを無視(ignore)する。 |
| 82 | + |
| 83 | +- observe: 契約違反時にハンドラを呼び出し、プログラムの実行を続行します。 |
| 84 | + |
| 85 | +- enforce: 契約違反時にハンドラを呼び出し、プログラムを終了します。 |
| 86 | + |
| 87 | +- quick_enforce: 契約違反時にハンドラを呼び出さず、即座にプログラムを終了します。 |
| 88 | + |
| 89 | +評価モードは、コンパイル時、もしくは実行時に指定できる。 |
| 90 | + |
| 91 | +GCCでは、 |
| 92 | +```bash |
| 93 | +g++ -std=c++26 -fcontracts -fcontract-semantic=observe main.cpp |
| 94 | +``` |
| 95 | +のように指定できる。 |
| 96 | + |
| 97 | +### 契約違反ハンドラ |
| 98 | +契約違反が発生した場合、`std::contracts::contract_violation`型の情報(<contracts>ヘッダー)がハンドラに渡されます。この情報には、違反の種類、発生場所、違反した条件式などが含まれます。 |
| 99 | + |
| 100 | +### 使用上の注意 |
| 101 | +以下の操作は、気をつけなければならない。 |
| 102 | +- 契約式内において副作用を要する式を記述した場合(グローバル変数の変更、`volatile`変数への参照、`constexpr`でない関数の呼び出しなど) |
| 103 | +- 契約式内で例外を送出すると、std::terminate()が呼び出され、プログラムが終了する。 |
| 104 | +- 通常の関数やメンバ関数には契約を適用できるが、特殊な関数(例えば`default`によって定義されたコピーコンストラクタやデストラクタ)には適用できない。適用すると、プログラムは不正(ill-formed)となる。 |
| 105 | + |
| 106 | +## <a id="relative-page" href="#relative-page">関連項目</a> |
| 107 | +- [C++ 将来 契約に基づくプログラミング](lang/future/contract-based_programming.md) |
| 108 | + |
| 109 | + |
| 110 | +## 参照 |
| 111 | +- [P2900R14 `Contracts for C++`](https://open-std.org/jtc1/sc22/wg21/docs/papers/2025/p2900r14.pdf) |
0 commit comments