
I came up with a solution which would involve renaming boost::exception_detail::clone_base to boost::exception_clone_base, documenting its requirements, and modifying boost::enable_current_exception to be able to detect if the type of its argument derives from boost::exception_clone_base. In that case, it wouldn't wrap it, and thus it is up to the user-defined exception type to initialize any virtual bases, including the ones that can't be default-initialized.
Good idea. Should have thought about it myself. Somehow I failed to do this.
See the attached patch -- I've only tested with MSVC but it should work with other compilers too. I'm not committing this to trunk yet. It seems acceptable, but I'm not sure if this extra complication is worth the trouble. I doubt that user code that requires a similar workaround exists in practice.
How to apply the patch to normally downloaded Boost? We use an official release (1.39) which we build ourselves. And the use it. Is is enough to replace the original files with those in the patch? We work on MS VC as well (2005 with SP). I haven't seen yet the changes but are they so complicated that you have to think whether they are worth applying?
Was your observation mostly theoretical, or was it based on trouble you had with existing code?
Currently I am introducing exception handling into already existing project (rather large but not extremely large). The project used exceptions before but we wanted to establish a common philosophy and policies for throwing, catching handling and so on, refractor the code appropriately and provide documentation for all this. So I started playing with the task. I decided to use Boost.Exception. The arbitrary data injection was what convinced me (however I am somewhat afraid catastrophic cases when std::bad_alloc is thrown during data injection and original exception is lost). One of exceptions I defined was WinAPIException which was constructible from DWORD error code. I wanted it to return the stored error code and return retrieved error message upon request basing on the error code. This cannot be provided without initializing the stored error code and thus my post. The solutions I had were: 1) To inherit from WinAPIException non-virtually. This seemed rather safe as I suspected that likelihood that a class will in result inherit from WinAPIException more then once is rather low and the base classes like std::exception and boost::exception would be inherited virtually anyway. But this seemed inconsistent and does not go well with other components. I had for example CombinedException which took as template parameters some exception classes and inherit from them all. This was useful since I had clear separation on logic errors and runtime errors (expressed in appropriate base class) and sometimes it was not known for an exception class whether it will be thrown in logic error or runtime error until it was actually thrown. The WinAPIException is a good example. It might be due to an WinAPI error like ERROR_INVALID_HANDLE which is (usually) due to a logic error or a ERROR_FILE_NOT_FOUND which is (usually) due to a runtime error. This could be decided only in place the exception is thrown so I had something like: if ( errorCode == ERROR_INVALID_HANDLE ) throw CombinedException< MyComponentException, WinAPIException, LogicException >( WinAPIException( errorCode ) ); else if ( errorCode == ERROR_FILE_NOT_FOUND ) throw CombinedException< MyComponentException, WinAPIException, IOException, RuntimeException >( WinAPIException( errorCode ) ); else .... (The code skips additional staff like using boost::throw_exception, adding function/file name/line number data and so on...) The CombinedException class is build using Boost.Preprocessor and it can copy-construct any subset of base classes (the caller knows which subset is required - in this case only WinAPIException). It saves us from defining an enormous number of exception classes or reducing type information (tagging) of the thrown exceptions. And to sum up having to inherit from WinAPIException non-virtually makes the CombinedException more complicated as it has to somehow know which classes are to be inherited virtually and which not. 2) Skip having non-trivial WinAPIException and use the Boost.Exception data injection alone. This makes however throwing much more complicated. How can I be sure that thrower will in fact inject the error code? I cannot. As I don't like trusting and hoping for the caller to remember to do the right thing I would have to provide some functions/macros to do that. This again makes it a little bit more complicated. Also this does not go well with legacy code/third party code/different policy code which might use non-trivial exceptions types. We would not be able to use them in such case. And I don't like when some policy does not give options. However note that I still experiment with the code trying to establish the policy. I still have to answer some questions. General requirements are to (random order): 1) Make declaring exceptions classes simple and short when no additional data is required. Empty classes (with base classes) are great here and data injection goes well with it. 2) Make throwing expressions short and simple. Here data injection and composites like the mentioned CombinedException are not good at all (just look at the above example code!). 3) Support exception wrapping on the module boundary (so that module has to care only for exceptions of the modules it uses directly) - and this I still don't know how to do as I have no good ideas for wrapping which will not loose a lot of data on the original exception. 4) Go well with somewhat different approaches to exceptions from "read-only modules". 5) Provide as much diagnostic data as possible. Currently it seems that we will almost never use any data in exceptions for other then diagnostic reasons. But that still has to be investigated. Adam Badura