C++ C++;noexcept用于未引发异常的函数,但可能导致内存故障

C++ C++;noexcept用于未引发异常的函数,但可能导致内存故障,c++,noexcept,C++,Noexcept,例如,有两种不同的方法访问私有数组的元素是很常见的,一种是重载数组订阅运算符,另一种是在处定义: 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!");

例如,有两种不同的方法访问私有数组的元素是很常见的,一种是重载数组订阅运算符,另一种是在处定义

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
版本的
可以引发异常,但数组订阅运算符不能

我的问题是,尽管
操作符[]
不会引发异常,但它是否可以标记为
noexcept
,即使知道它会引发SIGSEGV信号,还是仅仅是一种不好的做法

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

但是,一个
noexcept
函数比这更有意义,至少对我代码的客户来说是这样
noexcept
还隐式表示函数是安全的,它将在没有计算错误的情况下完成其执行


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

一个选项是添加边界检查,并使超出范围的值执行一些有意义的操作,例如返回last,如下所示

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

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

这是一个棘手的问题,并提出了其中涉及的一些关键问题,即,我将尝试在这里引用最低限度(强调我的):

在这篇文章中,我想分享一下我对使用noexcept真正增加价值的地方的观察。它的出现频率比人们预期的要低,它与抛出或不抛出异常没有多大关系。这个结论让我有点吃惊[很强],我不愿意提出它,因为它与我从权威人士身上听到的关于这个问题的意见相反。p> 以及:

考虑到对noexcept的这种消极态度,它能被认为是有用的吗?对noexcept特性在C++11中引入得很晚,以解决移动语义的一个特殊问题。道格拉斯·格雷戈(Douglas Gregor)和大卫·亚伯拉罕(David Abrahams)在这里描述了这一点

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

[…]这是因为noexcept真正想要的信息 传达的是功能永不失效;并不是说它从不扔!我们 从上面可以看出,函数可以失败,但仍然不能抛出,但是 仍然符合noexcept(false)的条件。也许关键字应该有 被称为诺法尔。“永不失效”担保不能由供应商进行检查 编译器(与任何其他故障安全保证非常相似),因此 我们唯一能做的就是申报

这是一个更普遍的观察的一部分,我们是什么 真正感兴趣的是程序组件中的故障安全,而不是 而不是例外安全。无论是否使用异常,都会返回错误 价值观,错误还是什么

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

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

他还指出。这反过来又是我们的基础。本文件对窄合同和宽合同的定义与N3248相同:

广泛合同

一项功能或操作的广泛契约并不 指定任何未定义的行为。这种合同没有任何先决条件: 具有广泛契约的函数不会放置额外的运行时 对其参数、任何对象状态或任何外部 全球国家。具有广泛合同的职能示例如下: vector::begin()和vector::at(大小\类型)。例子 没有广泛约定的函数将是vector::front()和 向量::运算符[](大小\类型)

狭义合约

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

并建议:

每个图书馆职能部门都有LWG同意的广泛合同 无法抛出,应标记为无条件noexcept

并意味着具有狭义契约的函数不应是
noexcept,该函数的备份说明:

[…]这些设计考虑凌驾于我们针对狭义合同功能的一般政策之上。[……]


因此,如果我们想保持保守并遵循标准库实践,那么似乎由于
操作符[]
没有广泛的契约,因此不应该将其标记为
noexcept

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

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