15

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?

ABu
  • 10,423
  • 6
  • 52
  • 103
  • 4
    A signal is not an exception. – ABu May 13 '15 at 18:29
  • 2
    This is definitely a solid question. But as you said 'a signal is not an exception', `noexcept` simply states that it won't throw exceptions - which it doesn't - but doesn't make any other guarantees about abnormal behavior (i.e. produces undefined behavior / restarts your system / formats your hard drive, etc.) – sircodesalot May 13 '15 at 18:32
  • 6
    `noexcept` is more than just a marker for functions that in their current implementation do not throw exceptions: It is also a guarantee for the user that this function *will never* throw exceptions. That is, users will depend on this guarantee, and you won't be able to add bounds checking later (e.g. in a test/debug config) without risking to abort the whole program. – dyp May 13 '15 at 18:36
  • @dyp That's an answer. – T.C. May 13 '15 at 18:39
  • Yes, that's right. If I decide to ignore bound checkings now, I can't checking it later. I agree with you. But that isn't my question (although it's a good point). I've refreshed my question to make more explicit what I'm trying to ask. – ABu May 13 '15 at 18:48
  • *"`noexcept` also implicitly says the function is safe and it will finish its execution without computational errors."* This would only be a convention. There's nothing in the semantics of `noexcept` that implies it. I have never seen such a convention - and, on the other hand, seen functions marked as `noexcept` that do have Undefined Behaviour, e.g. if preconditions are violated. – dyp May 13 '15 at 18:51
  • Note that even though you can introduce signal handlers for `SIGSEGV` via `std::signal`, the implied pointer arithmetic in an invalid out-of-bounds access is still undefined behavior. (Edit: Hmm, my question was stupid, actually, nvm) – Columbo May 13 '15 at 18:51
  • @dyp N3279 seems like a convention in that vain. – Shafik Yaghmour May 14 '15 at 15:48
  • @ShafikYaghmour Not necessarily: It is a conservative application of `noexcept` - I think we all agree that a no-fail, no-throw function can/should be marked as `noexcept`. However, the user cannot deduce from `noexcept` that the function cannot fail. For example, N3279 also suggests ("may") applying `noexcept` to *"Library functions designed for compatibility with “C” code"*. So nofail & nothrow => noexcept, but not noexcept => nofail. (Per convention, `vector` etc assume that for special member functions, noexcept => nofail.) – dyp May 14 '15 at 16:11

3 Answers3

6

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.

Marco A.
  • 43,032
  • 26
  • 132
  • 246
Shafik Yaghmour
  • 154,301
  • 39
  • 440
  • 740
  • Yes. I agree with that quotes. But in the other side, if I don't declare these non-throwig function as `noexcept`, I don't allow the compiler to apply its optimizations for noexcept functions. So, perhaps there should be two keywords, noexcept and nofail (with a semantic in such a way the later implies the former; but the opposite is not true). – ABu May 13 '15 at 21:27
  • @Peregring-lk I updated my answer with the proposal that covers the standard library practice which I find helpful in thinking about this. I would also see this [article as well](https://akrzemi1.wordpress.com/2011/06/10/using-noexcept/) it is old but detailed. – Shafik Yaghmour May 14 '15 at 15:04
1

The fact: either you use noexcept or not, the function can still throw SIGSEGV.

Now, I think the answer is subjective and depends on one's personal perspective. What would you expect from a noexcept function? Would you expect that it never fails (even if noexcept does not offer such guarantee)? How will you handle a non-safe function in comparison with a "safe" function? Will it be a big difference in the project? If your answer is yes, than you perhaps should not use noexcept.

On the other hand, by not using it (when you know for certain it cannot throw exceptions) would make another programmer worry about providing a try-clause when calling this function.

From Bjarne Stroustroup "The C++ Programming Language", 4th edition:

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?

My personal point of view is No, it is not bad practice or "immoral" to use it, all the more so as it can have some benefits.

0

One option is to add bounds checking and cause an out of range value to do something meaningful, such as return last as in the following.

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

This is non standard but is, in my opinion, acceptable if you clearly document the behavior. This allows for a no exception and no signal guarantee.

Benilda Key
  • 2,836
  • 1
  • 22
  • 34