C ++ noexcept用于不抛出异常的函数,但可能导致内存故障

时间:2015-05-13 18:27:59

标签: c++ noexcept

例如,有两种不同的方法来访问私有数组的元素,重载数组下标运算符或定义at,这是很常见的:

T& operator[](size_t i) { return v[i]; }
T const& operator[](size_t i) const { return v[i]; }

T& at(size_t i)
{
     if (i >= length)
         throw out_of_range("You shall not pass!");

     return v[i];
}

T const& at(size_t i) const
{
     if (i >= length)
         throw out_of_range("You shall not pass!");

     return v[i];
}

at版本可以抛出异常,但是数组下标运算符不能。

我的问题是,尽管operator[]没有抛出异常,它是否可以标记为noexcept,即使知道它可以引发SIGSEGV信号,还是只是一种不好的做法?

我想指出一个信号(如SIGSEGV)不是例外。作为字面解释noexcept含义,noexcept函数声称它不会抛出异常。它没有任何其他内容(包括信号)。

但是,noexcept函数具有更多意义,至少对于我的代码的客户而言。 noexcept也隐含地说该函数是安全的,它将在没有计算错误的情况下完成执行。

那么,将noexcept标记为一个不安全的函数是不合适的吗?

3 个答案:

答案 0 :(得分:5)

这是一个棘手的问题,并提出了noexcept — what for? from Andrzej's C++ blog 中涉及的一些关键问题,我将尝试引用最小值(强调我的):

  

在这篇文章中,我想分享我对使用noexcept真正增加价值的观察。它比人们预期的要少,而且与投掷或不投掷异常没有那么多关系。结论让我感到惊讶,我对此表示犹豫不决,因为这与我听到的有关该主题的权威人士的建议背道而驰。

  

鉴于这种对noexcept的消极态度,它是否可以被视为有用?是。 noexcept功能在C ++ 11中很晚才引入,以解决移动语义的一个特定问题。 Douglas Gregor和David Abrahams在此描述过。

然后他接着给出了一个不寻常的移动分配定义并且认为我们真正想传达的不是它不会抛出异常而是它不会失败,但这是非常困难的问题,但它是真实的意图:

  

[...]这是因为noexcept真正意图的信息   传达的是功能永远不会失败;不是它永远不会抛出!我们   可以看到上面的函数可能会失败但仍然不会抛出,但它   仍然有资格获得noexcept(false)。也许关键字应该有   被称为nofail。无法保证永不失败的保证   编译器(很像任何其他故障安全保证),因此   我们唯一能做的就是宣布它。

     

这是一个更普遍的观察的一部分,我们是什么   感兴趣的是程序组件中的失败安全性   比例安全。无论您是否使用异常,都会返回错误   值,错误或什么

     

除此之外,关于基本(无泄漏,不变保留),强(提交或回滚)和永不失败保证的推理仍应保留。

因此,如果我们采取类似的立场,似乎答案是否定的,如果不适合使用noexcept,那似乎就是你倾向于的地方。我不认为这是明确的答案。

他还注意到proposal N3248: noexcept Prevents Library Validation。这反过来又是N3279: Conservative use of noexcept in the Library的基础。本文定义了狭义和宽泛的合同,N3248:

  

广泛合同

     

功能或操作的广泛合同没有   指定任何未定义的行为。这样的合同没有先决条件:   具有宽合约的函数不会添加额外的运行时   对其参数,任何对象状态以及任何外部参数的约束   全球国家。具有广泛合同的职能的例子是   vector :: begin()和vector :: at(size_type)。示例   没有广泛合同的函数将是vector :: front()和   vector :: operator [](size_type)。

     

缩小合约

     

狭义合同是一份不宽的合同。狭义的合同   在a中调用时,函数或操作会导致未定义的行为   违反记录合同的方式。这样的合同   指定至少一个涉及其参数object的前提条件   状态,或某些外部全局状态,例如a的初始化   静态对象。缩小标准函数的好例子   合约是vector :: front()和vector :: operator [](size_type)   

并建议:

  

每个图书馆功能都有广泛的合同,LWG同意   不能扔,应该被无条件标记为noexcept。

并暗示具有窄合同的函数不应为noexcept,由LWG issue 2337备份,其中包含:{/ p>

  

[...]这些设计考虑因素超越了我们关于窄合约函数noexcept的一般政策。 [...]

因此,如果我们想保守并遵循标准的图书馆惯例,那么看起来operator[]没有广泛的合同就不应该标记为noexcept

答案 1 :(得分:1)

事实:要么你使用 noexcept ,函数仍然可以抛出 SIGSEGV

现在,我认为答案是主观的,取决于个人的观点。您对 noexcept 功能的期望是什么?您是否期望它永远不会失败(即使 noexcept 不提供此类保证)?与“安全”功能相比,您将如何处理非安全功能?这个项目会有很大的不同吗?如果您的答案是肯定的,那么您可能不应该使用 noexcept

另一方面,通过不使用它(当你确定它不能抛出异常时)会让另一个程序员担心在调用这个函数时提供try-clause。

来自 Bjarne Stroustroup C ++编程语言”,第4版:

  

声明函数noexcept对于程序员来说可能是最有价值的   关于程序和编译器优化程序的推理。该   程序员不必担心提供try-clauses(用于处理   在noexcept函数中失败并且优化器不用担心   关于异常处理的控制路径。

     

那么,将 noexcept 标记为不是的函数是不合适的   安全

我个人的观点是不,使用它是不错的做法或“不道德”,更是如此,因为它可以带来一些好处。

答案 2 :(得分:0)

一个选项是添加边界检查并导致超出范围值以执行有意义的操作,例如最后返回,如下所示。

T& operator[](size_t i)
{
    static T default;
    if (i >= length && length > 0)
    {
        return v[length - 1];
    }
    else if (i >= length && length == 0)
    {
        return default;
    }
    return v[i];
}

这是非标准的,但在我看来,如果您清楚地记录行为,则可以接受。这允许没有例外,也没有信号保证。