28

I am curious about the rationale behind noexcept in the C++0x FCD. throw(X) was deprecated, but noexcept seems to do the same thing. Is there a reason that noexcept isn't checked at compile time? It seems that it would be better if these functions were checked statically that they only called throwing functions within a try block.

AndyG
  • 39,700
  • 8
  • 109
  • 143
rlbond
  • 65,341
  • 56
  • 178
  • 228
  • 9
    Also, page 1107 has a nice footnote that is pretty funny. – rlbond May 04 '10 at 00:38
  • 2
    Ha ha! Nice find. That's as good as the redefinition of "ill-formed program" to be "a wannabe C++ program that is not well-formed" in N3035. – James McNellis May 04 '10 at 00:42
  • The problem with checking these statically is that 1) it can't really be done robustly, and 2) even if that was done, it'd still be a major pain to work with -- witness the problems with Java's equivalent. No one really wants statically checked exception specifiers in C++. The question is more whether exception specifiers should be there *at all* – jalf May 04 '10 at 01:53
  • 9
    Actually, the problem with the funny footnotes is that it makes it easy to mistake a real footnote for a funny one, e.g., "342) Among other implications, atomic variables shall not decay." – James McNellis May 04 '10 at 15:42

6 Answers6

27

Basically, it's a linker problem, the standards committee was reluctant to break the ABI. (If it were up to me, I would do so, all it really requires is library recompilation, we have this situation already with thread enablement, and it's manageable.)

Consider how it would work out. Suppose the requirements were

  1. every destructor is implicitly noexcept(true)
    • Arguably, this should be a strict requirement. Throwing destructors are always a bug.
  2. every extern "C" is implicitly noexcept(true)
    • Same argument again: exceptions in C-land are always a bug.
  3. every other function is implicitly noexcept(false) unless otherwise specified
  4. a noexcept(true) function must wrap all its noexcept(false) calls in try{}catch(...){}
    • By analogy, a const method cannot call a non-const method.
  5. This attribute must manifest as a distinct type in overload resolution, function pointer compatibility, etc.

Sounds reasonable, right?

To implement this, the linker needs to distinguish between noexcept(true) and noexcept(false) versions of functions, much as you can overload const and const versions of member functions.

So what does this mean for name-namgling? To be backwards-compatible with existing object code we would require that all existing names are interpreted as noexcept(false) with extra mangling for the noexcept(true) versions.

This would imply we cannot link against existing destructors unless the header is modified to tag them as noexcept(false)

  • this would break backwards compatibility,
  • this arguably ought to be impossible, see point 1.

I spoke to a standards committe member in person about this and he says that this was a rushed decision, motivated mainly by a constraint on move operations in containers (you might otherwise end up with missing items after a throw, which violates the basic guarantee). Mind you, this is a man whose stated design philosophy is that fault intolerant code is good. Draw your own conclusions.

Like I said, I would have broken the ABI in preference to breaking the language. noexcept is only a marginal improvement on the old way. Static checking is always better.

spraff
  • 32,570
  • 22
  • 121
  • 229
  • static checking would be better if it were possible. If a noyhrow function in module A calls a nothrow function in library B, thats fine. Until library B changes to be a throwing operation. – Mooing Duck Mar 13 '13 at 15:29
  • 4
    "_every extern "C" is implicitly noexcept(true)_" I (and others) would object to this. There is nothing stopping you throwing an exception from an `extern "C"` function, and on some implementations you can do so even if it's called by C code, letting the exception propagate back to C++ code that knows how to handle it. The exception-handling model used by GCC and Clang is explicitly design to support that, and Solaris supports it too. This is well defined and useful. Without C functions being implicitly non-throwing static checking of exception specs won't work. So we don't statically check. – Jonathan Wakely Mar 13 '13 at 18:11
  • 1
    I don't know if it's true that GCC/Clang supporting cross-language exceptions but **it's not supported by the language**. Letting an exception propagate into code which does not define exception handling is UB. C does not define exception handling. The Right Way is to catch the exception before reaching C and wrapping it into some other structure. Throwing exceptions from C is absurd. – spraff Mar 15 '13 at 16:48
  • 1
    Can this be checked statically by the compilers with the current C++11, generating warnings if these constraints are not met (except for the overload resolution)? If it can, then it's almost as good as an error (if that warning is turned into an error). – Alex Mar 23 '13 at 22:01
  • Perhaps I'm some kind of extremist but I don't think correctness should be a compiler option. – spraff Mar 28 '13 at 13:53
  • POSIX cancellation points are implemented with exceptions on some platforms. That means those C functions can throw. Of course prior to C11 using threads caused 'undefined behavior' in C too, but people still did it. Adding an implicit `noexcept(true)` to C functions would cause useful, conforming language extensions to become non-conforming. (It would be similar to C11 having added requirements that prevented threads as an extension instead of having added explicit support for threads.) Also, destructors throwing is _not_ always a bug, and is in fact sometimes useful. – bames53 Oct 30 '14 at 17:30
  • How can POSIX cancellation points be implemented with exceptions if they're written in C? (If they're not written in C, your complaint isn't relevant.) And perhaps "bug" isn't the right word but a throwing destructor always indicates you're doing something fundamentally wrong. Okay it may be *useful* but only within the context of a faulty design. Fix the design, don't break the language to accommodate it. Not every idea should be expressible. – spraff Nov 06 '14 at 21:45
  • The original paper http://www.open-std.org/JTC1/SC22/WG21/docs/papers/2009/n2855.html proposes exactly the static variant of `noexcept` OP is asking about. So is this one more (big, IMHO) thing we could implement if c++ chose to do an ABI break? – Danra Jun 19 '20 at 07:24
25

If I remember throw has been deprecated because there is no way to specify all the exceptions a template function can throw. Even for non-template functions you will need the throw clause because you have added some traces.

On the other hand the compiler can optimize code that doesn't throw exceptions. See "The Debate on noexcept, Part I" (along with parts II and III) for a detailed discussion. The main point seems to be:

The vast experience of the last two decades shows that in practice, only two forms of exceptions specifications are useful:

  • The lack of an overt exception specification, which designates a function that can throw any type of exception:

    int func(); //might throw any exception
    
  • A function that never throws. Such a function can be indicated by a throw() specification:

    int add(int, int) throw(); //should never throw any exception
    
yuri kilochek
  • 12,709
  • 2
  • 32
  • 59
Vicente Botet Escriba
  • 4,305
  • 1
  • 25
  • 39
  • 6
    I don't see how this answer explains the reason for why noexcept is not statically checked. Also, Mr Kalev's phrase "noexcept which serves as a compile-time exception specification" seems to contradict the C++ standard as well as this question. – sellibitze Mar 14 '13 at 15:16
8

Note that noexcept checks for an exception thrown by a failing dynamic_cast and typeid applied to a null pointer, which can only be done at runtime. Other tests can indeed be done at compile time.

outis
  • 75,655
  • 22
  • 151
  • 221
  • 1
    However, it is possible to simply forbid `dynamic_cast` or `typeid` outside a `try` block... – rlbond May 05 '10 at 21:15
  • 2
    Except that it might not be suitable to catch the exception at that point. The exception could be re-thrown, but that's adding unnecessary verbosity and overhead. – outis May 05 '10 at 21:59
6

As other answers have stated, statements such as dynamic_casts can possibly throw but can only be checked at runtime, so the compiler can't tell for certain at compile time.

This means at compile time the compiler can either let them go (ie. don't compile-time check), warn, or reject outright (which wouldn't be useful). That leaves warning as the only reasonable thing for the compiler to do.

But that's still not really useful - suppose you have a dynamic_cast which for whatever reason you know will never fail and throw an exception because your program is written so. The compiler probably doesn't know that and throws a warning, which becomes noise, which probably just gets disabled by the programmer for being useless, negating the point of the warning.

A similar issue is if you have a function which is not specified with noexcept (ie. can throw exceptions) which you want to call from many functions, some noexcept, some not. You know the function will never throw in the circumstances called by the noexcept functions, but again the compiler doesn't: more useless warnings.

So there's no useful way for the compiler to enforce this at compile time. This is more in the realm of static analysis, which tend to be more picky and throw warnings for this kind of thing.

AshleysBrain
  • 22,335
  • 15
  • 88
  • 124
3

Consider a function

void fn() noexcept
{
   foo();
   bar();
}

Can you statically check if its correct? You would have to know whether foo or bar are going to throw exceptions. You could force all functions calls to be inside a try{} block, something like this

void fun() noexcept
{
    try
    {
         foo();
         bar();
    }
    catch(ExceptionType &)
    {
    }
}

But that is wrong. We have no way of knowing that foo and bar will only throw that type of exception. In order to make sure we catch anything we'd need to use "...". If you catch anything, what are you going to do with any errors you catch? If an unexpected error arises here the only thing to do is abort the program. But that is essentially what the runtime check provided by default will do.

In short, providing enough details to prove that a given function will never throw the incorrect exceptions would produce verbose code in cases where the compiler can't be sure whether or not the function will throw the wrong type. The usefulness of that static proof probably isn't worth the effort.

Winston Ewert
  • 44,070
  • 10
  • 68
  • 83
  • 1
    I disagree. If we have a complete prototype for foo and bar then we will know if they have exception specifications. The only way this code will compile (even if foo and bar are in dynamicly loaded libraries) is if we have proper prototypes for them at compile time. – SoapBox May 04 '10 at 02:02
  • 1
    @SoapBox: But the only non-deprecated specifications are "might throw" and "doesn't throw." Whether or not `fun` throws when `foo` and `bar` have the "might throw" specifications is based on *what* they might throw. If they throw `ExceptionType` objects, `fun` is fine. If they throw `OtherExceptionType` objects, `fun` is not fine. The prototypes are insufficient to determine this. – Dennis Zickefoose May 04 '10 at 02:16
  • Also, what if the prototype changes from nothrow to not nothrow? – Mooing Duck Mar 13 '13 at 15:32
3

There is some overlap in the functionality of noexcept and throw(), but they're really coming from opposite directions.

throw() was about correctness, and nothing else: a way to specify the behavior if an unexpected exception was thrown.

The primary purpose of noexcept is optimization: It allows/encourages the compiler to optimize around the assumption that no exceptions will be thrown.

But yes, in practice, they're not far from being two different names for the same thing. The motivations behind them are different though.

jalf
  • 243,077
  • 51
  • 345
  • 550
  • 2
    The primary purpose of `noexcept` is correctness also. Throwing moves break the strong guarantee in previously correct code (such as sorting algorithms and container resizes) so we need `std::move_if_noexcept` to rewrite old generic code in a safe way. Of course the lack of static checking makes it behave like `assert` rather than `const` but that's the price of backwards compatibility :-( – spraff Oct 04 '11 at 09:04
  • As spraff says, template code such as containers can behave differntly based on the presence or absence of `noexcept` (and equivalently `throw()`) so it's not just about compiler optimizations, but also impacts library design and choice of algorithm. The key to doing that is the `noexcept` operator that allows code to query how throwy an expression is, _that's_ the new thing, and all that cares about is a yes/no answer, it doesn't care what type of exception might be thrown, only whether one might be thrown or not – Jonathan Wakely Mar 13 '13 at 18:14