4

Recently I tried to answer what I thought would be a simple question on the noexcept exception specification. The end result being that I found that my fundamental understanding of noexcept was wrong.

While reading the current draft standard to correct my misunderstanding, I found myself asking a few questions about noexcept that weren't answered here.

  1. Should noexcept be considered a safety guarantee, that the function when called will not only not throw but will not corrupt state?
  2. Assuming that (1.) is false: Is it correct to use noexcept as a portable FailFast to terminate the application without cleanup to prevent corruption of saved state?

Clarification to (2.): The intent is ONLY to prevent destructor calls further up the stack from the noexcept not to prevent unwinding within it. This is based on the assumption that this is a perfect RAII environment and the destructors up the stack could flush out global state to persistence thus corrupting it.

Example of how unwinding is not preformed:

#include <iostream>
#include <exception>

namespace{
   struct foo{
       void change_state() noexcept
       {
          // change state and fail
          throw std::exception();
       }
       ~foo(){
          std::cout << "Destructor called, saved state corrupted!" <<std::endl;
       }
    };
}


int main(){
    ::std::set_terminate([](){
        std::cout<< "Terminate called" <<std::endl;
    });

    foo f;
    f.change_state();
    return 0;
}

Working example of noexcept

Community
  • 1
  • 1
Mgetz
  • 5,108
  • 2
  • 33
  • 51
  • as I understand `noexcept` : even if this thing throws I don't wanna know about it. So it doesn't really works for "safety" or "correctness", it works by preventing an exception from being thrown so your program doesn't ends because of that. – user2485710 Feb 17 '14 at 13:49
  • 1
    If an exception reaches the outermost block of a function marked as `noexcept` then `std::terminate` is called. It does not suppress exceptions at all. It provides a barrier across which exceptions may not be thrown – Mgetz Feb 17 '14 at 13:56
  • what I wrote before is about _runtime_, `noexcept` works at compile time, so it's an hint for the compiler, the compiler evaluates your programs and if there is a chance that something throws, you have the chance to correct this before _runtime_ because the `noexcept` operator will fail at _compile time_ . It works like `type traits` in some ways, you define a property that needs to be evaluated to `true`, if not the compiler warns you, if the test pass you know that at _runtime_ you will have your types as defined in your `traits` conditions. Same thing for `noexcept`. – user2485710 Feb 17 '14 at 14:39
  • 1
    I don't think there are reasons to believe 1) (function can always corrupt state, even without exceptions), but I would say that 2) is correct. – Xarn Feb 17 '14 at 14:40
  • @user2485710 I think you fail to understand `noexcept` please read the linked standard in the question above. That is absolutely NOT what `noexcept` does. It is perfectly legal to have a function that is marked `noexcept` that has a `throw` statement in it and even to not `catch` said throw. The result of that will however be a call to `std::terminate`. – Mgetz Feb 17 '14 at 14:51
  • can you provide some code ? A self contained example ? – user2485710 Feb 17 '14 at 14:57

2 Answers2

2
  1. Yes. A function should not corrupt state, period, whether it throws or not. The fact that it might have wanted to throw an exception is immaterial. [Of course, there's no guarantee that it won't crash the program. That sort of behavior should probably be documented, though. ;-)]
  2. Mu. A function shouldn't bother throwing if it expects to throw through a noexcept; it should just call std::terminate (or std::abort) instead.
Sneftel
  • 40,271
  • 12
  • 71
  • 104
  • I understand what you're getting at that such failure should be explicit and not rely on the behavior of `noexcept`. I suppose I'm poking at the theoretical when the function marked `noexcept` calls a function that throws. Do you catch the exception and then call `std::terminate`? – Mgetz Feb 17 '14 at 15:30
  • 1
    Eh... if it's useful for the outer function to catch and handle the exception, then of course it should do so. After that, it should probably call `terminate` to demonstrate that it isn't bothering to maintain the class invariants. One of the things `throw` means is "but don't worry, we're okay". With that said, if the `throw` is an inch of screen space away from `noexcept`, it doesn't matter that much. – Sneftel Feb 17 '14 at 15:35
1

noexcept is a contractual guarantee, much like an assert which can't be turned off. If a function marked noexcept tries to exit via an exception, a post-condition has been violated, and you can't count on anything; the program will be terminated. Thus, 1 is false.

With regards to 2, it is more difficult. The implementation may unwind the stack up until it tries to leave the function with the noexcept; it's not 100% clear (at least to me) that it may not unwind the stack further. If I want to terminate the application without cleanup, then I'll call abort(), which is guaranteed to exit immediately without any further actions. It also tells the reader exactly what is happening, rather than counting on some secondary effect of a different feature.

James Kanze
  • 150,581
  • 18
  • 184
  • 329
  • so you would wrap any unsafe (non-`noexcept`) calls in this particular case with a `catch`? and then call `std::abort` to prevent further unwinding? (I am assuming that unwinding within the `noexcept` function will not damage saved state) – Mgetz Feb 17 '14 at 16:37
  • @Mgetz Normally, I wouldn't make any unsafe calls:-). Otherwise, I'd only wrap them if I wanted to swallow them (often the case of a destructor, for example, which could be called during stack unwinding). – James Kanze Feb 17 '14 at 16:46
  • It would seem my question is not clear... the intent is only to prevent destructor calls further up the stack than the `noexcept`. But if I'm understanding you... what you're saying is that in such cases as global saved state may be corrupted it's better to act as if in a `noexcept` context and then just `abort` if needed immediately. – Mgetz Feb 17 '14 at 16:50
  • @Mgetz When you detect an error, it's best to clear out immediately, doing as little as possible. Whether it's worth extra effort to detect the error immediately, or only when you leave the function, is another question. Most of the time, the functions involved should be small enough that it doesn't matter, and isn't worth the extra effort. – James Kanze Feb 17 '14 at 19:20