C++ 属性: expects, ensures, assert (C++20)

来自cppreference.com
< cpp‎ | language‎ | attributes

为函数指定前提条件、后置条件和断言。

目录

[编辑] 语法

[[ expects contract-level(可选) : expression ]] (1) (C++20 起)
[[ ensures contract-level(可选) identifier(可选) : expression ]] (2) (C++20 起)
[[ assert contract-level(可选) : expression ]] (3) (C++20 起)
contract-level - defaultauditaxiom 之一;默认为 default
identifier - 被用来代表函数返回值的标识符;任何关于某内容是 contract-level 还是 identifier 的歧义,以偏向它是 contract-level 的方式解决
expression - 按语境转换成 bool 的表达式,指定契约的谓词;其顶层运算符不可为赋值或逗号运算符

[编辑] 解释

1) 定义前提条件,即函数对其参数的期待和/或到函数入口中时其他对象的状态。此属性可应用到函数声明中的函数类型。在立即开始求值函数体(包括构造函数的成员初始化器列表)前,通过求值前提条件的谓词检查它。以词序检查同一函数的多个前提条件。
2) 定义后置条件,即函数应该确保的返回值和/或从函数退出时其他对象的状态的条件。此属性可应用到函数声明中的函数类型。若存在 identifier ,则它表示函数所返回对象的泛左值结果或纯右值结果,其中的可应用者。在立即返回控制到函数的调用方前(在局部变量和临时量的生存期结束后),通过求值后置条件的谓词检查它。以词序检查同一函数的多个后置条件。
3) 定义断言,即在函数体中出现时应该满足的条件。此属性可应用到空语句。断言由断言谓词的求值检查,该求值是断言所应用到的空语句的求值的一部分。

契约属性中可按语境转换成 bool 的表达式 expression 被称为谓词。谓词的求值必须无任何副效应,除了修改生存期始于并终于求值内的非 volatile 对象;否则行为未定义。若谓词的求值通过异常退出,则调用 std::terminate

在常量表达式求值期间,仅求值被检查的契约的谓词。所有其他语境中,不指定是否求值未检查的契约的谓词;若它求值为 false 则行为未定义。

[编辑] 契约条件

前提条件和后置条件合称为契约条件。这些属性可应用到函数声明中的函数类型:

int f(int i) [[expects: i > 0]] [[ensures audit x: x < 1]]; 
 
int (*fp)(int i) [[expects: i > 0]]; // 错误:不是函数声明

函数的首个声明必须指定函数的所有契约条件(若存在)。后继的重声明必须不指定契约条件,或指定同一契约条件列表;如果对应的条件始终求值为相同值,则不要求诊断。若在二个不同的翻译单元中声明同一函数,则契约条件列表应当相同;不要求诊断。

若二个契约条件列表以相同顺序含有相同的契约条件,则它们相同。若二个契约条件是同种契约条件,并拥有相同的 contract-level 和相同的谓词,则它们相同。若二个谓词,假设是出现在函数中的定义就会满足单一定义规则,除了重命名函数和模板形参和返回值标识符(若存在),则它们相同。

int f(int i) [[expects: i > 0]];
int f(int);                       // OK :重声明
int f(int j) [[expects: j > 0]];  // OK :重声明
int f(int k) [[expects: k > 1]];  // 病式
int f(int l) [[expects: 0 < l]];  // 病式,不要求诊断

若友元声明是翻译单元中函数的首个声明,且拥有契约条件,则该声明必须是定义,而且必须是该翻译单元中唯一的声明:

struct C {
   bool ok() const;
   friend void f(const C& c) [[ensures: c.ok()]]; // 错误:不是定义
   friend void g(C c) [[expects: c.ok()]] { } // OK
};
void g(C c); // 错误

契约条件的谓词拥有如同它是出现在其所应用到的函数的体内的首条表达式语句的语义限制。

若后置条件 odr 使用其谓词中的参数,而函数体直接或间接修改该参数的值,则行为未定义。

int f(int x) [[ensures r: r == x]]
{
  return ++x; // 未定义行为
}
int g(int* p) [[ensures: p != nullptr]]
{
  *p = 42; // OK :不修改 p
}
 
bool meow(const int&) { return true; }
 
void h(int x) [[ensures: meow(x)]] 
{
  ++x;  // 未定义行为
}
 
void i(int& x) [[ensures: meow(x)]]
{
  ++x;  // OK :引用的“值”是其所引用者,而且不能修改
}

[编辑] 构建等级与违规处理

程序能以三个构建等级之一翻译:

  • 关闭:不进行契约检查。
  • 默认(若无构建等级则为默认):对 contract-leveldefault 的契约进行检查。
  • 审核:对 contract-leveldefaultaudit 的契约进行检查。

选择构建等级的机制是实现定义的。组合翻译于不同构建等级的翻译单元为条件性支持。

程序的违规处理void (const std::contract_violation &) 类型函数(可选地为 noexcept ),以实现定义方式指定。在被检查契约的谓词求值为 false 时调用该函数。

  • 若违背前提条件,则反映于 std::contract_violation 参数的源码位置是实现定义的。
  • 若违背后置条件,则反映于 std::contract_violation 参数的源码位置是函数定义的源码位置。
  • 若违背断言,则反映于 std::contract_violation 参数的源码位置是断言所应用到的语句的源码位置。

其他情况下传递给违规处理函数的 std::contract_violation 参数值是实现定义的。

若违规处理函数通过抛异常退出,而契约在调用拥有不抛出异常规定的函数时被违规,则调用 std::terminate

 
void f(int x) noexcept [[expects: x > 0]];
void g() {
    f(0); // 若违规处理函数抛出则 terminate
}

程序可用二种违规持续模式翻译:

  • 关闭(若不选择持续模式则为默认):违规处理执行完成后,调用 std::terminate
  • 开启:违约处理执行完成后,正常持续。

鼓励实现不提供任何程序上的方式查询、设置或修改构建等级,或者设置或修改违规处理函数。