Namespaces
Variants

Contract assertions (since C++26)

From cppreference.net
C++ language
General topics
Flow control
Conditional execution statements
Iteration statements (loops)
Jump statements
Functions
Function declaration
Lambda function expression
inline specifier
Dynamic exception specifications ( until C++17* )
noexcept specifier (C++11)
Exceptions
Namespaces
Types
Specifiers
constexpr (C++11)
consteval (C++20)
constinit (C++20)
Storage duration specifiers
Initialization
Expressions
Alternative representations
Literals
Boolean - Integer - Floating-point
Character - String - nullptr (C++11)
User-defined (C++11)
Utilities
Attributes (C++11)
Types
typedef declaration
Type alias declaration (C++11)
Casts
Memory allocation
Classes
Class-specific function properties
Special member functions
Templates
Miscellaneous

契约断言允许程序员指定程序状态在特定执行点预期应满足的属性。

目录

说明

契约断言 函数契约说明符 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 的生命周期持续至契约违规处理器调用结束。

契约终止的程序

当程序被 合约终止 时,是否执行以下操作由实现定义(取决于上下文):

契约违反处理器

程序的 契约违规处理器 是一个名为 :: 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

1) 构造合约断言列表 S ,需满足以下所有条件:
  • R 的所有元素都包含在 S 中。
  • R 的每个元素可在 S 中重复实现定义次数。
  • 若合约断言 A R 中先于另一合约断言 B ,则 A 的首次出现应在 S 中先于 B 的首次出现。
2) S 的每个元素进行求值,使得若契约断言 A S 中先于契约断言 B ,则 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) 契约

关键词

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