【问题标题】:C++ noexcept for a function not throwing exceptions, but can cause a memory failureC++ noexcept 用于不抛出异常但可能导致内存故障的函数
【发布时间】:2015-07-25 04:48:29
【问题描述】:

例如,有两种不同的方法来访问私有数组的元素是很常见的,重载数组下标运算符,或者定义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[] 不会引发异常,但即使知道它可以引发 SIGSEGV 信号,是否也可以将其标记为 noexcept,还是只是一种不好的做法?

我想指出一个信号(如 SIGSEGV)也不例外。从字面上解释noexcept 的含义,noexcept 函数声称它不会抛出异常。它没有说明其他任何事情(包括信号)。

但是,noexcept 函数的意义远不止于此,至少对于我的代码的客户而言。 noexcept 还隐含地表示该函数是安全的,它将在没有计算错误的情况下完成其执行。

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

【问题讨论】:

  • 信号也不例外。
  • 这绝对是一个可靠的问题。但是正如您所说的“信号不是异常”,@ 987654331@ 只是声明它不会抛出异常 - 它不会 - 但不会对异常行为做出任何其他保证(即产生未定义的行为/重新启动您的系统/格式化您的硬盘等)
  • noexcept 不仅仅是在当前实现中不抛出异常的函数的标记:它也是对用户的保证,该函数永远不会抛出例外。也就是说,用户将依赖此保证,并且您将无法在以后添加边界检查(例如在测试/调试配置中)而不冒中止整个程序的风险。
  • @dyp 这就是答案。
  • 是的,没错。如果我现在决定忽略绑定检查,我以后就不能再检查了。我同意你的看法。但这不是我的问题(尽管这是一个好点)。我已经刷新了我的问题,以便更明确地提出我要问的问题。

标签: c++ noexcept


【解决方案1】:

一种选择是添加边界检查并导致超出范围的值做一些有意义的事情,例如返回最后一个,如下所示。

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];
}

这是非标准的,但在我看来,如果你清楚地记录了行为,这是可以接受的。这允许无异常和无信号保证。

【讨论】:

    【解决方案2】:

    这是一个棘手的问题,并提出了noexcept — what for? from Andrzej's C++ blog 中涵盖的一些关键问题,其中说,我将尝试在此处引用最低限度(强调我的):

    在这篇文章中,我想分享我对使用 noexcept 真正增加价值的地方的观察。它比人们预期的要少,并且 它与抛出或不抛出异常没有太大关系。这个结论让我有点惊讶,我犹豫是否要提出它,因为这与我从我认为有关该主题的权威人士那里听到的建议背道而驰。

    和:

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

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

    [...]这是因为 noexcept 真正想要的信息 传达的是功能永远不会失败;并不是说它从不抛出!我们 可以在上面看到一个函数可能会失败但仍然不会抛出,但是它 仍然符合 noexcept(false) 条件。也许关键字应该有 被称为不失败。永不失败的保证不能由 编译器(很像任何其他故障安全保证),因此 我们唯一能做的就是声明它。

    这是一个更普遍的观察的一部分,即我们是什么 真正感兴趣的是程序组件中的故障安全,而不是 比异常安全。不管你是否使用异常,错误返回 值,errno 或什么

    无论如何,关于基本(无泄漏、保持不变)、强(提交或回滚)和永不失败保证的推理应该仍然成立。

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

    他还注意到proposal N3248: noexcept Prevents Library Validation。这又是N3279: Conservative use of noexcept in the Library 的基础。这篇论文定义了窄合约和宽合约,就像 N3248 一样:

    广泛的合同

    函数或操作的广泛契约不 指定任何未定义的行为。这样的合同没有先决条件: 具有广泛合同的函数不需要额外的运行时间 对其参数、任何对象状态或任何外部的约束 全局状态。具有广泛合同的函数的示例是 vector::begin() 和 vector::at(size_type) 。示例 没有广泛合同的函数将是 vector::front() 和 向量::运算符[](size_type)。

    窄合约

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

    并推荐:

    每个库函数都有广泛的合同,LWG 同意 不能抛出,应该标记为无条件noexcept。

    并暗示具有狭义合约的函数不应该是noexcept,它由LWG issue 2337支持,它说:

    [...]这些设计考虑超越了我们反对 noexcept 窄合约功能的一般政策。 [...]

    因此,如果我们想保守一点并遵循标准库实践,那么似乎因为operator[] 没有广泛的合同,它不应该被标记为noexcept

    【讨论】:

    • 是的。我同意那些报价。但另一方面,如果我不将这些 non-throwig 函数声明为 noexcept,我不允许编译器将其优化应用于 noexcept 函数。所以,也许应该有两个关键字,noexcept 和 nofail(以这样的方式语义,后者暗示前者;但反之则不然)。
    • @Peregring-lk 我用涵盖标准库实践的提案更新了我的答案,我认为这有助于思考这个问题。我也会看到这个article as well 它很旧但很详细。
    【解决方案3】:

    事实:无论你是否使用 noexcept,该函数仍然可以抛出 SIGSEGV

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

    另一方面,不使用它(当您确定它不会抛出异常时)会使另一个程序员担心在调用此函数时提供 try 子句。

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

    声明一个函数 noexcept 对程序员来说是最有价值的 推理程序和优化程序的编译器。这 程序员不必担心提供 try 子句(用于处理 noexcept 函数失败)和优化器不必担心 关于异常处理的控制路径。

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

    我个人的观点是不,使用它不是坏习惯或“不道德”,更重要的是它可以带来一些好处。

    【讨论】:

      猜你喜欢
      • 2014-03-15
      • 2011-07-04
      • 2021-01-04
      • 1970-01-01
      • 2013-03-25
      • 1970-01-01
      • 2019-06-18
      • 1970-01-01
      • 2016-07-13
      相关资源
      最近更新 更多