如何检查和处理违反先决条件的行为?

Mor*_*hai 1 c++ software-design code-contracts c++20

C ++ 20围绕合同提供了一些令人惊叹的新功能-对于模板来说,这将使生活变得更好-可以将围绕类型或其他编译时要求的约束放入模板定义中,并由编译器进行适当的诊断。好极了!

但是,我非常担心在发生运行时先决条件冲突时无条件终止的努力。

https://en.cppreference.com/w/cpp/language/attributes/contract

可以使用两种违规继续模式之一来翻译程序:

关(如果未选择任何连续模式,则为默认值):违反处理程序的执行完成后,将调用std :: terminate; on:违反处理程序的执行完成后,将继续正常执行。鼓励实现不提供任何编程方式来查询,设置或修改构建级别或设置或修改违规处理程序。

我已经编写了广泛的面向用户的软件,该软件将所有异常捕获到一个核心执行循环中,在该循环中记录错误并通知用户有关失败的信息。

在许多情况下,用户最好尽可能地保存并退出,但在其他许多情况下,可以通过更改他们正在处理的设计/数据文件中的内容来解决错误。

这就是说,仅通过更改其设计(例如CAD设计),他们希望执行的操作即可成功。例如,代码执行时的公差可能过紧,从而无法根据该结果计算结果。更改容差后,只需重新运行该过程即可成功(不再违反基础代码中某个地方的违规前提条件)。

但是推动前提条件的努力简单地终止了,没有能力捕获这种错误并重试该操作吗?这听起来像是功能集的严重降级。诚然,在某些领域中确实需要这样做。快速失败,提早失败,并且对于先决条件或后置条件,问题出在编写代码的方式上,并且用户无法补救这种情况。

但是...这是一个很大的问题,但是...大多数软件都会针对运行时提供的未知数据集执行-声称所有软件都必须终止,并且无法期望用户纠正这种情况似乎很奇怪。

赫伯·萨特(Herb Sutter)在ACCU的讨论似乎与前提条件和后置条件违反仅仅是终止条件的观点紧密相关:

https://www.youtube.com/watch?v=os7cqJ5qlzo

我正在根据您的编码经验告诉您其他C ++专业人员在想什么?

我知道许多项目都不允许例外。如果您正在从事一个这样的项目,这是否意味着您编写代码以在发生无效输入时简单地终止?还是您使用错误状态回退到能够以某种方式继续的某些父代码点?

也许更重要的是-也许我误解了C ++ 20运行时合同的意图本质?

请保持这种文明的态度-如果您的建议是结束这种做法-也许您会友好地指向一个更合适的论坛进行讨论?

总的来说,我正在努力让我满意:

如何检查和处理违反先决条件的行为(使用最佳实践)?

Nic*_*las 13

这实际上归结为这个问题:当您说“前提”一词时,您什么意思

您似乎使用该词的方式是指“调用此函数时会检查的事物”。Herb,C ++标准以及C ++合约系统的意思是“对于有效执行此功能,它必须是正确的;如果不正确,则您做错了,世界就是坏了。”

这种观点实际上归结为“合同”的含义。考虑vector::operator[]vector::at()at在C ++标准中没有前提条件合同;如果索引超出范围,则抛出该异常。也就是说,它是接口一部分at,您可以将其传递给超出范围的值,并且它将以预期的可预测的方式响应。

情况并非如此operator[]。您可以传递超出范围的索引,而不是该函数的接口的一部分。因此,它具有前提条件约定,即索引不会超出范围。如果将其传递给超出范围的索引,则会得到未定义的行为

因此,让我们看一些简单的例子。我将先构建一个vector,然后从用户读取一个整数,然后使用该整数vector以三种不同的方式访问我构建的整数:

int main()
{
    std::vector<int> ivec = {10, 209, 184, 96};

    int ix;
    std::cin >> ix;

    //case 1:
    try
    {
        std::cout << ivec.at(ix);
    }
    catch(const std::exception &)
    {
        std::cout << "Invalid input!\n";
    }

    //case 2:
    if(0 <= ix && ix < ivec.size())
        std::cout << ivec[ix];
    else
        std::cout << "Invalid Input!\n";

    //case 3:
    std::cout << ivec[ix];

    return 0;
}
Run Code Online (Sandbox Code Playgroud)

在情况1中,我们看到的使用at。在输入错误的情况下,我们会捕获异常并进行处理。

在情况2中,我们看到了的使用operator[]。我们检查输入是否在有效范围内,如果是,则调用operator[]

在情况3中,我们看到... 代码中的错误。为什么?因为没有人对输入进行消毒。有人必须这样做,并且operator[]的前提条件是这样做是呼叫者的工作。调用者无法清理其输入,因此代表了损坏的代码。

那就是建立合同的意思:如果代码违反了合同,那是代码违反合同的错。

但是正如我们所看到的,合同似乎是功能接口的基本组成部分。如果是这样,为什么接口的这一部分位于标准的文本中,而不是位于人们可以看到的函数的可见声明中?权利就是合同语言功能的全部重点:允许用户在语言中表达这种特定的事物。

总而言之,合同是假设一段代码构成了世界的状态。如果该假设不正确,则表示该状态不应该存在,因此您的程序中存在错误。这就是合同语言功能设计的基础。如果您的代码对其进行了测试,那么您就不会假设它,也不应使用前提条件来定义它。

如果是错误情况,则应使用首选的错误机制,而不是合同。

  • 好。您设法为我发现的问题提出了一个很好的答案。+1。我撤回了我的近距离投票,并删除了之前的评论。说得好。 (3认同)