契约断言 (C++26 起)
契约断言允许程序员指定程序在执行中的特定时间点的状态应有的属性。
目录 |
[编辑] 解释
契约断言 由函数契约标识符和 contract_assert
语句引入。每个契约断言都有一个谓词,它是 bool 类型的表达式。
[编辑] 对契约断言求值
对契约断言求值会使用以下求值语义之一:
求值语义 | 是否为检查语义 | 是否为终止语义 |
---|---|---|
忽略 | ||
观察 | 是 | |
强制 | 是 | 是 |
快速强制 | 是 | 是 |
每次对契约断言求值时使用的求值语义都由实现定义。同一个契约断言的多次求值(包括在常量求值过程中的求值)也可以使用不同的求值语义。
如果使用“忽略”语义,那么对契约断言求值没有效果。
如果使用检查语义,那么对契约断言的求值 E
会确定谓词的值。未指定是否会对谓词求值。如果满足以下任意条件,那么就会发生契约违背:
存在一个先发生于 E
的可观察检查点 CP
,使得先发生于 A
的任何其他操作 OP
也先发生于 CP
。
int num = 0; void f() pre((num++, false)); f(); // 即使使用检查语义,也不一定会自增 “num”
[编辑] 处理契约违背
如果在明显常量求值语境中发生了契约违背,那么:
- 如果求值语义是“观察”,那么会产生一条诊断信息。
- 如果求值语义是某个终止语义,那么程序非良构。
如果在明显常量求值语境以外的语境中发生了契约违背,那么:
- 如果求值语义是“快速强制”,那么程序会被契约终止。
- 如果求值语义是“强制”或“观察”,那么会以一个指代包含了契约违背信息的 const std::contracts::contract_violation 类型对象 obj 的左值调用契约违背处理函数。
- obj 使用的存储会以未指定的方式分配,但保证不会调用全局分配函数。
- obj 的生存期会在契约违背处理函数的整个调用期间持续。
[编辑] 被契约终止的程序
当程序被契约终止 时,由实现定义(取决于语境)
- 是否会调用 std::terminate,
- 是否会调用 std::abort,以及
- 是否会终止执行(后续不会再有执行步骤发生)
[编辑] 契约违背处理函数
每个程序都有一个名为 ::handle_contract_violation 的契约违背处理函数:
void handle_contract_violation( std::contracts::contract_violation ); |
(C++26 起) (可为 noexcept) |
|
由实现(而不是某个标准库标头)提供契约违背处理函数的某个被称为默认契约违背处理函数 的定义。
契约违背处理函数是否可替换由实现定义。如果契约违背处理函数不可替换,那么契约违背处理函数的任何声明都非良构,不要求诊断。
当契约违背处理函数正常返回时:
- 如果求值语义是“观察”,那么控制流会从对契约断言的求值之后继续流动。
- 如果求值语义是“强制”,那么程序会被契约终止。
存在一个后发生于契约违背处理函数正常返回时的可观察检查点 CP
,使得后发生于该契约违背处理函数返回时的任何其他操作 OP
也后发生于 CP
。
[编辑] 从断言处理异常
如果契约违背因为以异常退出谓词求值而发生,并且求值语义是“观察”或“强制”,那么会从为该异常隐式创建的一个活跃处理块中调用契约违背处理函数。
当契约违背处理函数正常返回时:
- 如果求值语义是“观察”,那么该隐式处理块不再活跃。
- 如果求值语义是“强制”,那么该隐式处理块在契约终止时保持活跃。
在契约违背处理函数中可以使用 std::current_exception() 检查或重抛当前异常。
[编辑] 依次求值
对包含契约断言的列表 R
通过以下方式依次求值:
S
:
-
S
包含R
的所有元素。 -
R
的每个元素都可以在S
中重复由实现定义的次数。 - 如果在
R
中契约断言A
先序于另一契约断言B
,那么在S
中契约断言A
的首次出现也先序于另一契约断言B
的首次出现。
void f(int i) { contract_assert(i > 0); // #1 contract_assert(i < 10); // #2 // 合法求值序列:#1 #2 (不重复) // 合法求值序列:#1 #1 #2 #2 (依次重复) // 合法求值序列:#1 #2 #1 #2 (轮流重复) // 合法求值序列:#1 #2 #2 #1 (第二次出现可以不按顺序) // 非法求值序列:#2 #1 (第一次出现必须按顺序) }
[编辑] 注解
可选择的求值语义的范围和灵活度取决于实现,并且不需要可选择所有四种求值语义。
如果在不同翻译单元中对同一契约断言选择不同的求值语义,那么在契约断言具有可以改变常量表达式产生的值的副作用的情况下会违反单一定义规则:
constexpr int f(int i) { contract_assert((++const_cast<int&>(i), true)); return i; } inline void g() { int a[f(1)]; // 大小取决于以上 contract_assert 的求值语义 }
在假如对谓词求值时结果是 true 的情况下不会发生契约违背,并且控制流会从对契约断言的求值之后继续流动。
如果对谓词的求值以非局部跳转或终止程序来退出,那么也不会发生契约违背。
C++ 标准推荐默认契约违背处理函数以合适的方式格式化实参的最相关内容,并以该内容产生诊断输出(在有潜在的重复契约违背时控制输出频率),然后正常返回。
功能特性测试宏 | 值 | 标准 | 功能特性 |
---|---|---|---|
__cpp_contracts |
202502L |
(C++26) | 契约 |
[编辑] 关键词
[编辑] 支持状态
C++26 功能特性 |
提案 |
GCC |
Clang |
MSVC |
Apple Clang |
EDG eccp |
Intel C++ |
Nvidia HPC C++ (ex PGI)* |
Nvidia nvcc |
Cray
|
---|---|---|---|---|---|---|---|---|---|---|
契约 (FTM)* | P2900R14 |
[编辑] 参阅
contract_assert 语句 (C++26)
|
在执行中验证一项内部条件 |
函数契约说明符 (C++26) | 指定前条件(pre)和后条件(post) |