Boost logo

Boost :

Subject: Re: [boost] [exception] Virtual inheritance with no defaultconstructors
From: Emil Dotchevski (emildotchevski_at_[hidden])
Date: 2009-05-13 21:50:50


On Wed, May 13, 2009 at 1:32 AM, Adam Badura <abadura_at_[hidden]> wrote:
>> 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.

I think you can use http://gnuwin32.sourceforge.net/packages/patch.htm
to apply patch files on Windows.

>   I haven't seen yet the changes but are they so complicated that you have
> to think whether they are worth applying?

Not complicated, but it seems to me that it is unlikely that anyone
would need these changes in practice and if that's the case I'd rather
not commit them. Consider the downsides of having to document the
clone_base type; after all, once compilers start supporting
exception_ptr natively, all of these concerns will disappear.

>> Was your observation mostly theoretical, or was it based on trouble
>> you had with existing code?
>
>   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.

Here's how I handle this type of errors -- an example of calling
TerminateProcess:

struct exception_base: virtual std::exception, virtual boost::exception { };
struct win32_error: virtual exception_base { };
typedef boost::error_info<struct tag_win_last_error,unsigned> win32_last_error;
typedef boost::error_info<struct tag_c_function,char const *> c_function;

....

if( !TerminateProcess(hProcess,uExitCode) )
  BOOST_THROW_EXCEPTION(
    win32_error() <<
    win32_last_error(GetLastError()) <<
    c_function("TerminateProcess"));

A main advantage of Boost Exception is that your exception types don't
have to have any data members or constructors; see
http://www.boost.org/doc/libs/release/libs/exception/doc/exception_types_as_simple_semantic_tags.html.

>   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.

My approach is to write wrappers for WIN32 (and other C functions) I
call, which throw exceptions on failure. So, instead of calling
TerminateProcess, I call win32_TerminateProcess, which always throws
on error and adds the relevant error code, etc.

Your concern about possibly forgetting to add the error code is valid,
but if you call C functions directly (without a wrapper to throw on
failure) you have the problem that users might forget to check for
errors altogether -- and thus forget to throw, which is a bigger
problem than forgetting to add the error code to the exception.

If you have a few common items that should all be added to a
particular class of exceptions, you can use boost::tuple of
boost::error_info objects of types that don't have default
constructors, and while you can still "forget" to add the tuple
itself, you can't forget to add a particular member of the tuple (I
think). See http://www.boost.org/doc/libs/release/libs/exception/doc/tuple_operator_shl.html,
and "Adding Grouped Data to Exceptions" in
http://www.boost.org/doc/libs/release/libs/exception/doc/tutorial_transporting_data.html.

>   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.

In my experience most legacy code doesn't use virtual inheritance in
exception types. If that's the case, I don't see a reason to change it
to use virtual inheritance.

> 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.

Wrapping is not a good idea. If a module can handle an exception --
great, if not -- it shouldn't interfere (by changing the original
exception's type.)

> 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.

See http://www.boost.org/doc/libs/release/libs/exception/doc/tutorial_diagnostic_information.html.

Emil Dotchevski
Reverge Studios, Inc.
http://www.revergestudios.com/reblog/index.php?n=ReCode


Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk