Fun with exceptions
2010-08-04 3 Comments
Here’s a guideline for you: If your library uses exceptions, and you intend users of your library to catch them, don’t implement them inline in the header.
Example:
class LIB_EXPORT MyException : public std::exception { public: explicit MyException( ... ); // ... (but no dtor) };
Here, the compiler will generate a MyException
destructor for you, since you didn’t provide one. Compiler-synthesised destructors are public, inline, have an empty body, and the same exception specification as the base class’ destructor. So, the above is equivalent to:
class LIB_EXPORT MyException : public std::exception { public: explicit MyException( ... ); ~MyException() throw() {} // ... };
Unfortunately (well, actually fortunately, but not for our scenario), the destructor of std::exception
is virtual, so ~MyException() throw() {}
(explicitly written by you or synthesised by the compiler) is the definition (as opposed to declaration) of the first virtual function of the class MyException
. Most C++ compilers take that as a cue to emit the MyException
virtual function table, as well as its RTTI information at that point.
Oops. Does that mean we’ll end up with duplicate vtables and RTTI information for a class whose first virtual function is defined inline?
Yes, it does. That’s not normally a big problem, since the linker will merge them at link time. However, at least my GCC 4.3 doesn’t do this across shared library boundaries.
Thus, we end up with a situation where the throw site uses a different vtable (and std::type_info
instance) for the exception class than the user of the library (which has its own copy helpfully provided by the compiler). The result is that you can’t catch the exception in client code anymore:
// in the client try { doSomethingThatThrowsMyException(); } catch ( const MyException & e ) { // never hit log( "Caught MyException: %s", e.what() ); return; } catch ( const std::exception & e ) { // hit instead log( "Caught unexpected exception %s: %s:", typeid(e).name(), e.what() ); return; }
Now, consider the surprise—the sheer terror—of a developer using your library, only to find he’s thrown a MyException
that he can’t catch.
Don’t go there. Define your exception classes out-of-line!
[Update 2010-12-02: The problem only occurs if you throw the exception from out-of-line code. If you only throw the exception from inline code, everything is peachy.]
So that’s why I was bitten by exactly that when I tried to reliably catch exiv2 exceptions from KPhotoAlbum. I’ve checked their (exiv2’s) SVN and now it seems correct, even though the commit message was “merging changes from devel back to trunk” and I’m not really in the mood to dig in extinct SVN branches today — let’s hope they don’t revert back to inline stuff.
Thanks for a detailed explanation; it is perfectly clear now (in fact, it was clear when I read past the ” definition of the first virtual function” bit).
Too bad some of your posts are password protected, it seems that you write about an interesting topic. Anyway, have fun!
Great post! I had a very similar experience to Jan with exiv2 exceptions in Gwenview. It makes more sense now.
Interesting! I learned two valuable things from your post: a) an automatically defined dtor uses the same exception-specification as it’s base class’ dtor, b) compilers actually *do* omit vtables when they see it fit.
This might keep me from a few struggles in similar situations in the future.
Thanks!