Contract assertions (since C++26)
契约断言允许程序员指定程序状态在特定执行点预期应满足的属性。
目录 |
说明
契约断言
由
函数契约说明符
和
contract_assert
语句引入。每个契约断言包含一个
谓词
,这是一个类型为
bool
的表达式。
评估合约断言
契约断言的求值采用以下求值语义之一:
| 评估语义 | 检查语义 | 终止语义 |
|---|---|---|
| ignore | ||
| observe | 是 | |
| enforce | 是 | 是 |
| quick-enforce | 是 | 是 |
对于合约断言的任何给定求值,具体采用哪种求值语义是由实现定义的。同一合约断言的不同次求值可能采用不同的求值语义,包括常量求值期间的求值。
如果使用了“忽略”语义,合约断言的求值将不产生任何效果。
如果使用了检查语义,合约断言
E
的求值将决定谓词的值。是否对谓词进行求值是未指定的。若满足以下任一条件,则发生
合约违反
:
存在一个
可观测检查点
CP
,该检查点发生于
E
之前,使得任何其他发生于
A
之前的操作
OP
也必定发生于
CP
之前。
int num = 0; void f() pre((num++, false)); f(); // “num”的递增操作可能不会发生,即使使用了检查语义
处理契约违规
如果契约违反发生在明显常量求值的上下文中:
- 如果求值语义为“观察”,则会产生诊断信息。
- 如果求值语义为终止语义,则程序格式错误。
如果合约违规发生在非明显常量求值的上下文中:
- 若求值语义为“quick-enforce”,程序将终止契约执行。
-
若求值语义为“enforce”或“observe”,将调用契约违规处理器并传递一个左值引用,该引用指向类型为
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
之后发生。
处理断言抛出的异常
如果契约违反是由于谓词求值通过异常退出而发生,且求值语义为"observe"或"enforce",则契约违反处理程序会从该异常的活跃隐式 异常处理程序 内部被调用。
当合约违反处理程序正常返回时:
- 若求值语义为“observe”,则隐式处理程序不再被视为活跃状态。
- 若求值语义为“enforce”,则在合约终止发生时,隐式处理程序仍保持活跃状态。
当前异常可以在合约违反处理程序中使用 std::current_exception() 进行检查或重新抛出。
按顺序求值
要按顺序
求值
一个合约断言列表
R
:
S
,需满足以下所有条件:
-
R的所有元素都包含在S中。 -
R的每个元素可在S中重复实现定义次数。 -
若合约断言
A在R中先于另一合约断言B,则A的首次出现应在S中先于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) | 契约 |
关键词
contract_assert , pre , post
支持状态
|
C++26 特性
|
提案文档
|
GCC
|
Clang
|
MSVC
|
Apple Clang
|
EDG eccp
|
Intel C++
|
Nvidia HPC C++ (原 PGI)*
|
Nvidia nvcc
|
Cray
|
|---|---|---|---|---|---|---|---|---|---|---|
| Contracts ( FTM ) * | P2900R14 |
参见
contract_assert
语句
(C++26)
|
在执行期间验证内部条件 |
| 函数契约说明符 (C++26) | 指定前置条件( pre )和后置条件( post ) |