NoSenseEtAl
NoSenseEtAl

Reputation: 30028

Why noexcept is not enforced at compile time?

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.

Upvotes: 30

Views: 5565

Answers (4)

Jerry Coffin
Jerry Coffin

Reputation: 490108

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.

Upvotes: 25

tr3w
tr3w

Reputation: 343

The problem with compile-time checking: it's not really possible in any useful way. See the next example:

void foo(std::vector<int>& v) noexcept
{
    if (!v.empty())
        ++v.at(0);
}

Can this code throw? Clearly not. Can we check automatically? Not really. The Java's way of doing things like this is to put the body in a try-catch block, but I don't think it is better than what we have now...

Upvotes: 8

Mordachai
Mordachai

Reputation: 9662

As I understand things (admittedly somewhat fuzzy), the entire idea of throw specifications was found to be a nightmare when it actually came time to try to use it in useful way.

Calling functions that don't specify what they throw or do not throw must be considered to potentially throw anything at all! So the compiler, were it to require that you neither throw nor call anything that might throw anything outside of the specification you're provided actually enforce such a thing, your code could call almost nothing whatsoever, no library in existence would be of any use to you or anyone else trying to make any use of throw specifications.

And since it is impossible for a compiler to tell the difference between "This function may throw an X, but the caller may well be calling it in such a way that it will never throw anything at all" -- one would forever be hamstrung by this language "feature."

So... I believe that the only possibly useful thing to come of it was the idea of saying nothrow - which indicates that it is safe to call from dtors and move and swap and so on, but that you're making a notation that - like const - is more about giving your users an API contract rather than actually making the compiler responsible to tell whether you violate your contract or not (like with most things C/C++ - the intelligence is assumed to be on the part of the programmer, not the nanny-compiler).

Upvotes: 4

Pete Becker
Pete Becker

Reputation: 76295

One reason is simply that compile-time enforcement of exception specifications (of any flavor) is a pain in the ass. It means that if you add debugging code you may have to rewrite an entire hierarchy of exception specifications, even if the code you added won't throw exceptions. And when you're finished debugging you have to rewrite them again. If you like this kind of busywork you should be programming in Java.

Upvotes: 11

Related Questions