提问人:Mordachai 提问时间:5/3/2019 最后编辑:Mordachai 更新时间:5/4/2019 访问量:2039
如何检查和处理违反前提条件的行为?
How to check for and handle precondition violations?
问:
C++ 围绕合约提供了一些惊人的新功能 - 对于模板来说,这将使生活变得更好 - 其中围绕类型或其他编译时要求的约束可以融入模板定义中,并由编译器通过适当的诊断来强制执行。耶!
但是,我非常担心在发生运行时先决条件冲突时无条件终止的推动力。
https://en.cppreference.com/w/cpp/language/attributes/contract
一个程序可以用两个违规延续之一进行翻译 模式:
关闭(如果未选择延续模式,则默认):执行后 冲突处理程序完成时,将调用 std::terminate;上: 在违规处理程序的执行完成后,执行 正常继续。建议实现不要提供任何 查询、设置或修改生成级别或设置或 修改冲突处理程序。
我编写了大量面向用户的软件,它将所有异常捕获到核心执行循环中,在该循环中记录错误并通知用户失败。
在许多情况下,如果可能的话,用户最好保存并退出,但在许多其他情况下,可以通过更改他们正在处理的设计/数据文件中的某些内容来解决错误。
也就是说,只需更改他们的设计(例如CAD设计),他们希望执行的操作现在就会成功。例如,代码的执行容差可能太小,导致基于该容差的计算结果出错。只需在更改容差后重新运行该过程即可成功(底层代码中某处的违规前提条件将不再被违反)。
但是,推动前提条件简单地终止,并且没有能力捕获此类错误并重试操作?对我来说,这听起来像是功能集的严重退化。诚然,在某些领域,这完全是可取的。快速失败,早期失败,对于前置条件或后置条件,问题出在代码编写方式上,用户无法补救这种情况。
但。。。这是一个很大的但是...大多数软件都是针对运行时提供的未知数据集执行的 - 声称所有软件都必须终止,并且无法期望用户纠正这种情况似乎很奇怪。
赫伯·萨特(Herb Sutter)在ACCU上的讨论似乎与前置条件和后置条件的违规只是终止条件的观点非常一致:
https://www.youtube.com/watch?v=os7cqJ5qlzo
我正在寻找其他 C++ 专业人士的想法,无论您的编码经验告诉您什么?
我知道许多项目不允许例外。如果你正在处理一个这样的项目,这是否意味着你编写的代码在发生无效输入时直接终止?或者您是否使用错误状态回退到某个能够以某种方式继续的父代码点?
也许更切中要害 - 也许我误解了 C++20 运行时合约的意图的本质?
请保持这种文明 - 如果你的建议是关闭它 - 也许你可以指出一个更合适的论坛进行这个讨论?
一般来说,我试图回答,令我满意:
如何检查和处理前提条件违规(使用最佳实践)?
答:
这真的归结为这个问题:当你说“先决条件”这个词时,你是什么意思?
你似乎使用这个词的方式是指的是“当你调用这个函数时被检查的东西”。Herb,C++标准,因此C++合约系统意味着它是“一个必须为真才能有效执行这个函数的事情,如果它不是真的,那么你做错了事情,世界就破碎了。
这种观点实际上归结为“合同”的含义。考虑 vs. 在 C++ 标准中没有前提条件协定;如果索引超出范围,则抛出。也就是说,它是接口的一部分,您可以将它传递到范围之外的值,并且它将以预期的、可预测的方式进行响应。vector::operator[]
vector::at()
at
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;
}
在案例 1 中,我们看到 .在输入错误的情况下,我们会捕获异常并对其进行处理。at
在案例 2 中,我们看到 .我们检查输入是否在有效范围内,如果是,则调用 .operator[]
operator[]
在案例 3 中,我们看到......我们代码中的一个错误。为什么?因为没有人对输入进行消毒。有人必须这样做,并且前提条件说这是呼叫者的工作。调用方未能审查其输入,因此表示代码已损坏。operator[]
这就是建立契约的意义:如果代码破坏了契约,那就是代码破坏了契约。
但正如我们所看到的,合约似乎是函数接口的基本部分。如果是这样,为什么接口的这一部分位于标准的文本中,而不是在人们可以看到它的函数的可见声明中?这就是合约语言特性的全部意义所在:允许用户在语言中表达这种特定的东西。
总而言之,合同是一段代码对世界状况做出的假设。如果这个假设是不正确的,那么它代表了一种不应该存在的世界状态,因此你的程序中有一个错误。这就是合约语言功能设计的基本思想。如果你的代码测试了它,它不是你假设的,你不应该使用前提条件来定义它。
如果是错误条件,则应使用首选的错误机制,而不是协定。
评论