简体   繁体   中英

Why is C++0x's `noexcept` checked dynamically?

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.

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 

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.

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.

As other answers have stated, statements such as dynamic_cast s 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.

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.

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM