Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why noexcept is not enforced at compile time?

Tags:

As you might know C++11 has noexcept keyword. Now ugly part about it is this:

Note that a noexcept specification on a function is not a compile-time check; it is merely a method for a programmer to inform the compiler whether or not a function should throw exceptions.

http://en.cppreference.com/w/cpp/language/noexcept_spec

So is this a design failure on the committee part or they just left it as an exercise for the compile writers :) in a sense that decent compilers will enforce it, bad ones can still be compliant?

BTW if you ask why there isnt a third option ( aka cant be done) reason is that I can easily think of a (slow) way to check if function can throw or not. Problem is off course if you limit the input to 5 and 7(aka I promise the file wont contain anything beside 5 and 7) and it only throws when you give it 33, but that is not a realistic problem IMHO.

like image 712
NoSenseEtAl Avatar asked Jan 29 '13 22:01

NoSenseEtAl


People also ask

What happens when Noexcept function throws?

Note that noexcept doesn't actually prevent the function from throwing exceptions or calling other functions that are potentially throwing. Rather, when an exception is thrown, if an exception exits a noexcept function, std::terminate will be called.

Is Noexcept required?

Explicit instantiations may use the noexcept specifier, but it is not required. If used, the exception specification must be the same as for all other declarations.

Does Noexcept make code faster?

Declaring a function noexcept helps optimizers by reducing the number of alternative execution paths. It also speeds up the exit after failure. The keyword noexcept serves two purposes (like most of the language, really): It's an instruction to the compiler, and it's also helpful to humans who are reading the code.

What is the purpose of Noexcept?

The noexcept operator performs a compile-time check that returns true if an expression is declared to not throw any exceptions. It can be used within a function template's noexcept specifier to declare that the function will throw exceptions for some types but not others.


1 Answers

The committee pretty clearly considered the possibility that code that (attempted to) throw an exception not allowed by an exception specification would be considered ill-formed, and rejected that idea. According to $15.4/11:

An implementation shall not reject an expression merely because when executed it throws or might throw an exception that the containing function does not allow. [ Example:

 extern void f() throw(X, Y);   void g() throw(X) {       f(); // OK   } 

the call to f is well-formed even though when called, f might throw exception Y that g does not allow. —end example ]

Regardless of what prompted the decision, or what else it may have been, it seems pretty clear that this was not a result of accident or oversight.

As for why this decision was made, at least some goes back to interaction with other new features of C++11, such as move semantics.

Move semantics can make exception safety (especially the strong guarantee) much harder to enforce/provide. When you do copying, if something goes wrong, it's pretty easy to "roll back" the transaction -- destroy any copies you've made, release the memory, and the original remains intact. Only if/when the copy succeeds, you destroy the original.

With move semantics, this is harder -- if you get an exception in the middle of moving things, anything you've already moved needs to be moved back to where it was to restore the original to order -- but if the move constructor or move assignment operator can throw, you could get another exception in the process of trying to move things back to try to restore the original object.

Combine this with the fact that C++11 can/does generate move constructors and move assignment operators automatically for some types (though there is a long list of restrictions). These don't necessarily guarantee against throwing an exception. If you're explicitly writing a move constructor, you almost always want to ensure against it throwing, and that's usually even pretty easy to do (since you're normally "stealing" content, you're typically just copying a few pointers -- easy to do without exceptions). It can get a lot harder in a hurry for template though, even for simple ones like std:pair. A pair of something that can be moved with something that needs to be copied becomes difficult to handle well.

That meant, if they'd decided to make nothrow (and/or throw()) enforced at compile time, some unknown (but probably pretty large) amount of code would have been completely broken -- code that had been working fine for years suddenly wouldn't even compile with the new compiler.

Along with this was the fact that, although they're not deprecated, dynamic exception specifications remain in the language, so they were going to end up enforcing at least some exception specifications at run-time anyway.

So, their choices were:

  1. Break a lot of existing code
  2. Restrict move semantics so they'd apply to far less code
  3. Continue (as in C++03) to enforce exception specifications at run time.

I doubt anybody liked any of these choices, but the third apparently seemed the last bad.

like image 146
Jerry Coffin Avatar answered Nov 09 '22 18:11

Jerry Coffin