c++ - 如何检查和处理前提条件违规?

标签 c++ software-design code-contracts c++20

C++20 围绕契约提供了一些惊人的新特性——对于模板来说,这将使生活变得更美好——其中围绕类型或其他编译时要求的约束可以被纳入模板定义中,并通过适当的诊断来强制执行编译器。耶!

但是,我非常担心在发生运行时前提条件违规时无条件终止的插入。

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

A program may be translated with one of two violation continuation modes:

off (default if no continuation mode is selected): after the execution of the violation handler completes, std::terminate is called; on: after the execution of the violation handler completes, execution continues normally. Implementations are encouraged to not provide any programmatic way to query, set, or modify the build level or to set or modify the violation handler.

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

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

这就是说 - 只需更改他们的设计(例如 CAD 设计) - 他们希望执行的操作现在就会成功。例如。执行代码的容差可能太小,从而根据该容差计算出结果。只需在更改容差后重新运行该过程即可成功(不再违反底层代码中某处有问题的先决条件)。

但是使先决条件的插入简单地终止并且没有能力捕获这样的错误并重试操作?对我来说,这听起来像是功能集的严重退化。不可否认,在某些领域中这是完全可取的。快速失败,尽早失败,对于前置条件或后置条件,问题出在代码的编写方式上,用户无法补救。

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

Herb Sutter 在 ACCU 的讨论似乎与前提条件和后置条件违规只是终止条件的观点强烈一致:

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

我正在寻找其他 C++ 专家根据您的编码经验告诉您的想法?

我知道很多项目不允许异常(exception)。如果您正在从事一个这样的项目,这是否意味着您编写代码以在出现无效输入时简单地终止?或者您是否使用错误状态退出某些能够以某种方式继续的父代码点?

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

请保持文明 - 如果您的建议是关闭此 - 也许您可以指出一个更合适的论坛来进行此讨论?

一般来说,我正在尝试回答令我满意的问题:

如何检查和处理前提条件违规(使用最佳实践)?

最佳答案

真正归结为这个问题:当你说“前提”这个词时,你是什么意思

您使用该词的方式似乎是指“调用此函数时会检查的东西”。 Herb 的方式、C++ 标准以及 C++ 契约(Contract)系统意味着它是“为了有效执行此功能必须为真的东西,如果它不为真,那么你就做错了事并且 世界 splinter 了。”

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

operator[] 不是这种情况。 不是您可以将超出范围的索引传递给该函数的接口(interface)的一部分。因此,它有一个先决条件合约,即指数没有超出范围。如果您向它传递一个超出范围的索引,您将得到未定义的行为

那么,让我们看一些简单的例子。我将构建一个 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;
}

在案例 1 中,我们看到了 at 的使用。如果输入错误,我们会捕获异常并进行处理。

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

在案例 3 中,我们看到...代码中的错误。为什么?因为没有人清理输入。必须有人这样做,operator[] 的先决条件表明这是调用者的工作。调用方未能清理其输入,因此表示代码损坏。

这就是建立契约的意思:如果代码破坏了契约,那么破坏契约就是代码的错。

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

总而言之,契约是一段代码对世界状态做出的假设。如果该假设不正确,那么它代表了不应该存在的世界状态,因此您的程序中存在错误。这就是合约语言功能设计的基础思想。如果您的代码对其进行了测试,则它不是您假设的东西,您不应使用先决条件来定义它。

如果这是一个错误条件,那么您应该使用您首选的错误机制,而不是契约(Contract)。

关于c++ - 如何检查和处理前提条件违规?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55972696/

相关文章:

database - 为什么数据库不与 3D 和绘图软件一起使用(用于存储点和其他原始形状)?

c++ - OpenGL 不显示任何内容?

c++ - 如何在 C++ 中动态扩展数组? {就像 vector 中的}

architecture - ATAM 中效用树的用途

html - 寻找一种高效抽象内联样式的系统方法

c# - 由于代码契约(Contract),ASP NET WebApp 在发布后失败

c++ - 基于范围的循环:对扩展了一个元素的 vector 进行迭代

c++ - 程序收到信号 EXC_BAD_ACCESS - Mac OS X

c# - 如何让代码契约忽略特定的程序集引用?

c# - 接口(interface)的 ContractInvariant 方法