C++ noexcept 一个函数不抛出异常,但会导致内存故障

C++ noexcept for a function not throwing exceptions, but can cause a memory failure

例如,有两种不同的方法来访问私有数组的元素、重载数组下标运算符或定义 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 一个不安全的函数是不合适的吗?

一个选项是添加边界检查并导致超出范围的值做一些有意义的事情,例如下面的return 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 中涵盖的一些关键问题——有什么用?从 Andrzej 的 C++ 博客 说,我将尝试在此处引用最小值(强调我的 ):

In this post I would like to share my observation on where using noexcept really adds value. It is less often than what one might expect, and it does not have that much to do with throwing or not throwing exceptions. The conclusion surprises me a bit, and I hesitate to present it because it is counter to the advice I hear from people I consider authorities on the subject.

和:

Given this negative attitude to noexcept, can it be considered useful at all? Yes. The noexcept feature was introduced very late into C++11 to address one particular issue with move semantics. It has been described here by Douglas Gregor and David Abrahams.

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

[...]This is because the information that noexcept really is intended to convey is that the function never fails; not that it never throws! We can see above that a function can fail but still not throw, but it still qualifies for noexcept(false). Perhaps the keyword should have been called nofail. The never-fail guarantee cannot be checked by the compiler (much like any other failure-safety guarantee), therefore the only thing we can do is to declare it.

This is part of a more general observation, that what we are interested in is really failure safety in program components rather than exception safety. No matter if you use exceptions, error return values, errno or what

ever else, the reasoning about basic (no leak, invariant preserved), strong (commit or rollback) and never-fail guarantee should still hold.

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

他还注意到 提案 N3248:noexcept 阻止库验证。这又是 N3279: Conservative use of noexcept in the Library 的基础。本文与 N3248 一样定义了窄合同和宽合同:

Wide Contracts

A wide contract for a function or operation does not specify any undefined behavior. Such a contract has no preconditions: A function with a wide contract places no additional runtime constraints on its arguments, on any object state, nor on any external global state. Examples of functions having wide contracts would be vector::begin() and vector::at(size_type) . Examples of functions not having a wide contract would be vector::front() and vector::operator[](size_type) .

Narrow Contracts

A narrow contract is a contract which is not wide. Narrow contracts for a functions or operations result in undefined behavior when called in a manner that violates the documented contract. Such a contract specifies at least one precondition involving its arguments, object state, or some external global state, such as the initialization of a static object. Good examples of standard functions with narrow contracts are vector::front() and vector::operator[](size_type) .

并推荐:

Each library function having a wide contract, that the LWG agree cannot throw, should be marked as unconditionally noexcept.

并暗示具有窄契约的函数不应该是 noexceptLWG issue 2337 支持它说:

[...]These design considerations override our general policy against noexcept for narrow-contract functions. [...]

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

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

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

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

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

Declaring a function noexcept can be most valuable for a programmer reasoning about a program and for a compiler optimizing a program. The programmer need not worry about providing try-clauses (for dealing with failures in a noexcept function) and an optimizer need not worry about control paths from exception handling.

So, is it inappropiate to mark noexcept a function which isn't safe?

我个人的观点是不,这不是坏习惯,也不是 "immoral" 使用它,更重要的是它可以带来一些好处。