什么是C ++中的合同17

时间:2015-07-09 12:13:16

标签: c++ assertion design-by-contract c++17

我正在阅读B. Stroustrup在Thoughts about C++17中的合同,并协助一个小小的演讲,谈论他们,但我不确定我是否理解他们。

所以我有一些询问,如果有可能用一些例子说明它们:

  • 合同是否更好地替代了经典assert(),是否应该一起使用?对于软件开发人员来说,简单的合同是什么?

  • 合同会对我们处理例外的方式产生影响吗?如果是,我们应该如何使用例外和合同?

  • 使用合同是否意味着执行时的开销?我们是否允许在发布代码上停用它们?

proposal N4415编辑:

  

可以写出Vector类的索引操作符的前置条件合约:
  T& operator[](size_t i) [[expects: i < size()]];

     

类似地,ArrayView类的构造函数上的条件后约定可以表示为:   ArrayView(const vector<T>& v) [[ensures: data() == v.data()]];

3 个答案:

答案 0 :(得分:22)

据我从这份文件中读到: http://www.open-std.org/JTC1/SC22/WG21/docs/papers/2015/n4415.pdf

合同执行assert多年来一直以原始方式尝试做的事情。它们既是文档,也是运行时断言,调用者应该如何调用函数,调用程序在函数返回后期望代码处于什么状态。这些通常被称为前置条件和后置条件,或不变量。

这有助于清理实现方面的代码,因为通过契约我们可以假设一旦执行进入函数内部,你的参数就处于有效状态(你期望它们是什么)。

后置条件部分可能会改变您处理异常的方式,因为通过合同,您必须确保抛出异常不会破坏您的后置条件。这通常意味着您的代码必须是异常安全的,但这是否意味着强大的异常保证或基本保证取决于您的条件。

示例:

class Data;
class MyVector {
public:
    void MyVector::push_back(Elem e) [[ensures: data != nullptr]]
    {
        if(size >= capacity)
        {
            Data* p = data;
            data = nullptr; // Just for the sake of the example...
            data = new Data[capacity*2]; // Might throw an exception
            // Copy p into data and delete p
        }
        // Add the element to the end
    }
private:
     Data* data;
     // other data
};

在此示例中,如果newData的构造函数抛出异常,则会违反您的后置条件。这意味着您应该更改所有此类代码,以确保您的合同永远不会受到侵犯!

当然,就像assert一样,合同可能包含运行时开销。但不同之处在于,由于契约可以作为函数声明的一部分,因此编译器可以进行更好的优化,例如在调用者站点评估条件,甚至在编译时对它们进行评估。本文开头提到的文档的第1.5节讨论了根据您的构建配置关闭合同的可能性,就像普通的断言一样。

答案 1 :(得分:8)

我从提供的原始文件OP中的link开始。 我想有一些答案。我强烈建议从那篇论文开始。这是TL&amp; DR版本:

  

合同不是一般的错误报告机制,也不是   替代测试框架。相反,他们提供基本的   程序因错误而导致的缓解措施   程序各部分之间的期望不匹配。   合同在概念上更像是结构化的   assert()集成到语言中,由语言播放   语义规则 - 因此是原则程序分析的基础   和工具。

关于您的问题:

  • 它的结构化断言(),所以是的,可以说在某些情况下,assert()必须用合同替换。
  • 让我在这里使用另一个引用:
  

......合同的表达必须在逻辑上成为其中的一部分   宣布行动。

和示例:

T& operator[](size_t i) [[expects: i < size()]];

在我看来,这很好看且可读。

  • 在某些情况下,合同可以取代例外:
  

但是,合同可以在嵌入式系统或其他资源受限的系统中使用,这是一个关键的设计标准   提供例外。

在条件前合同中,仍然可以使用例外,因为在条件合同失败后的进一步行为无法保证。

  • 可以通过打开/关闭合同检查来减少开销:使用全部,仅使用非,前置条件,仅后置条件。 打开合同肯定会增加一些开销,就像任何类型的支票一样。

一些用例(我可以想到,虽然我甚至不接近开发合同设计)

  1. 合同 - 如果合同通常为assert() 更具可读性,可在编译时进行优化。
  2. 断言 - 在单元测试,测试框架等中。
  3. 例外 - 可以与预先签订的合同一起使用 在文章中提到:
  4.   

    在任何其他操作之前评估操作的前提条件   函数体中的语句。如果结果为真,则正常   执行控制继续到正文中的第一个语句   功能。否则,无法保证进一步执行:或者   程序中止,或抛出异常,或者如果允许的话   继续然后行为未定义。

    关于合同实施还有some other个建议,所以我们的调查还为时过早。

答案 2 :(得分:3)

要回答你的其他问题并不容易:这取决于。这是因为尚不清楚合同究竟是什么。现在有几个提议和想法浮出水面:

  • n4378 Lakos et al.基本上建议标准化复杂的断言工具包。在函数实现中检查契约,提供3个不同的断言级别来控制运行时检查的数量,并且可以自定义断言违规的处理。

  • n4415 dos Reis et al.n4435 Brown非常相似,并提出了一种基于属性的语法来定义函数接口中的前置条件和后置条件。他们没有详细说明他们对运行时检查和违规行为的控制程度。

最近关于这个话题的论文也不多了。有许多细节尚未确定,这个特征涉及许多不同的领域(例如模块,优化,建筑/链接),其中一些标准几乎无法控制。

关于异常的问题特别困难,因为合同违规处理和异常之间的相互作用不明确(例如合同违规处理程序可能会抛出(在测试框架中有用)?如果函数是noexcept(true)会怎样?)。