Exception specifications
Posted 06/10/2009
on:- In: mechanisms
- 9 Comments
C++ provides a mechanism that allows any function to declare exactly which exception types it may throw, and these declerations are actually enforced in runtime. We will review exactly how this mechanism works, and why it is usually left unused.
Here’s the proper syntax for providing a list of exceptions that may be thrown during the execution of a specific function:
// may throw only type A or type B (or derived types): void f () throw (A, B);
It is obviously possible to declare a function as nothrow, using empty parenthese:
// same as __declspec(nothrow): void func () throw ();
When a specification is omitted, C++ assumes the function can throw anything.
As a side note, the exception specification is not considered as part of the function’s signature.
When inheriting, the derived function must not throw more than the base function: if base function could only throw x, the derived function may only throw (derived from) x or nothing. This is called covariance [ we will also demand covariance in returned values (why?) ].
Exception specificaion can not be enforced in compile time (for many good reasons), therefore it is enforced in run time. Every use of the previously defined f() is basically transformed by the compiler to something along the lines of:
try { f(); } catch (const A &a) { throw; } catch (const B &b) { throw; } catch (...) { std::unexpected(); }
According to the standard (and as implemented above), the std::unexptected() function should be called when the exception specification is breached. It is possible to supply a user defined function to run instead of the default std::unexptected() by invoking std::set_unexpected().
If the function is defined as nothrow, the compiler assumes you know what you’re doing and is able to optimize away all of this exception handling. So if the code actually does throw something.. you’re going to be in a very bad place. Never use a nothrow specification unless you’re 100% certain that it’s indeed the correct situation and that it will remain so in the future.
Exception specifications encur runtime overhead due to the fact that they are enforced at runtime, and don’t have much added value. Well, other than extending the documentation of the interface by including possible exceptions — which is huge in my eyes, but still not worth the runtime penalty. This may be why they usually remain unsused.
However, using the empty throw() specification when appropriate is very important; In my opinion, functions that provide nothrow exception safety, such as destructors and de-allocators, must be marked as such (and allow extra compiler optimizations to take place).
Regarding the actual implementation of this feature on modern compilers, here’s what I came up with:
- On Microsoft’s Visual Studio (2008),
The only supported exception specification is the empty one. When you attempt to use a non-empty specification, the following warning is issued: “warning C4290: C++ exception specification ignored except to indicate a function is not __declspec(nothrow)” - On GNU G++ (4.3.3),
Looks like the mechanism is implemented as required by the standard. When I threw a wrong expcetion (one that was’nt in the throw() specification), std::terminate() was invoked through std::unexptected(), as required. Here is what I got at runtime (though not even a warning during compilation): “terminate called after throwing an instance of ‘C'”
9 Responses to "Exception specifications"
So you prefer the Java way for enforcing…. 🙂
Keep in mind that Herb Sutter discourages the usage of exception specifications (sources: http://www.gotw.ca/gotw/082.htm and http://www.gotw.ca/publications/mill22.htm). He says the Boost programmers (among whom you find a few people in the C++ standards committee) recommend not using them as well.
1 | lorg
06/10/2009 at 22:58
While I like the idea of exception specification, I think it’s too much bureaucracy.
Still, I disagree with your sentiment “which is huge in my eyes, but still not worth the runtime penalty”. What is the runtime penalty? Can you tell? Doesn’t it depend on whether or not the actual application you are developing is time critical?
If it is indeed time critical, the desired behavior I’d want is to be able to have a compiler flag to disable/enable exception specifications.
That way, you can enable them for tests and make sure you never[1] throw unexpected, and disable them for release builds.
[1] as certain as your code coverage by tests allows you to be.
rmn
07/10/2009 at 16:39
The runtime penalty is more in cases where the function MAY throw, but doesn’t actually (for example when you’re implementing an interface or foreseeing inheritance), and the exception specification actually prevents the compiler from optimizing.
For example, here’s why Boost doesn’t use exception specifications: http://www.boost.org/development/requirements.html#Exception-specification
The solution you suggest (compiler flag) is esentially the same as treating the exceptions you expect to happen and putting an assert(0) on a catch all (catch (…)). Then the assert will be removed on release builds automatically while still providing the needed checking during test phase.
However, I do believe that exception specifications are good as they provide a far better contract (that is also enforced) between the caller and the callee. The problem is in the fact that the enforcement is done in run time and not in compile time.. You would never want your program to terminate with an std::unexpected() in a released version.