Peregring-lk Peregring-lk - 13 days ago 6
C++ Question

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

For example, it's pretty common to have two separate ways to access elements of a private array, overloading the array subscripting operator, or defining

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


The
at
version can throw an exception, but the array subscripting operator can't.

My question is, altough the
operator[]
doesn't throw an exception, can it be marked as
noexcept
even knowing it can raise a SIGSEGV signal, or is it only a bad practice?

I want to point out a signal (as SIGSEGV) is not an exception. Being literal interpreting the
noexcept
meaning, a
noexcept
function is one that claims it won't throw exceptions. It says nothing about anything else (including signals).

But, a
noexcept
function has more meaning than that, at least for clientes of my code.
noexcept
also implicitly says the function is safe and it will finish its execution without computational errors.

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

Answer

This is a tough question and brings up some of the key issues covered in noexcept — what for? from Andrzej's C++ blog which says, I will attempt to quote the minimum here (emphasis mine):

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.

and:

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.

and then he goes on to given an unusual move assignment definition and argues that what we really want to convey is not that it does not throw exceptions but that it does not fail, but that is very difficult problem but it is out real intent:

[...]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.

So if we take a similar position then, it would seem the answer is no, if not appropriate to use noexcept and that seems to be where you are leaning. I don't think it is clear cut answer.

He also notes proposal N3248: noexcept Prevents Library Validation. Which in turn was the basis for N3279: Conservative use of noexcept in the Library. This paper defines narrow and wide contracts as did 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) .

and recommends:

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

and implies that functions with narrow contracts should not be noexcept, which is backed up by LWG issue 2337 which says:

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

So if we want to be conservative and follow standard library practice then it would seem since operator[] does not have a wide contract it should not be marked noexcept.

Comments