I was wondering maybe it would be better to use multiple inheritance to inherit from each of the derived std::exception classes
Note that this is a problem, due to the fact that the exceptions in the standard library derive non-virtually from each other. If you introduce multiple inheritance, you get the dreaded diamond exception hierarchy without virtual inheritance and won't be able to catch derived exceptions by std::exception&
, since your derived exception class carries two subobjects of std::exception
, making std::exception
an "ambiguous base class".
Concrete example:
class my_exception : virtual public std::exception {
// ...
};
class my_runtime_error : virtual public my_exception
, virtual public std::runtime_error {
// ...
};
Now my_runtime_error
derives (indirectly) from std::exception
twice, once through std::run_time_error
and once through my_exception
. Since the former doesn't derive from std::exception
virtually, this
try {
throw my_runtime_error(/*...*/);
} catch( const std::exception& x) {
// ...
}
won't work.
Edit:
I think I've seen the first example of an exceptions class hierarchy involving MI in one of Stroustrup's books, so I concluded that, in general, it is a good idea. That the std lib's exceptions do not derive virtually from each other I consider as a failure.
When I last designed an exception hierarchy, I used MI very extensively, but did not derive from the std lib's exception classes. In that hierarchy, there were abstract exception classes which you defined so your users could catch them, and corresponding implementation classes, derived from these abstract classes and from an implementation base class, which you would actually throw. To make this easier, I defined some templates which would do all the hard work:
// something.h
class some_class {
private:
DEFINE_TAG(my_error1); // these basically define empty structs that are needed to
DEFINE_TAG(my_error2); // distinguish otherwise identical instances of the exception
DEFINE_TAG(my_error3); // templates from each other (see below)
public:
typedef exc_interface<my_error1> exc_my_error1;
typedef exc_interface<my_error2> exc_my_error2;
typedef exc_interface<my_error3,my_error2> // derives from the latter
exc_my_error3;
some_class(int i);
// ...
};
//something.cpp
namespace {
typedef exc_impl<exc_my_error1> exc_impl_my_error1;
typedef exc_impl<exc_my_error2> exc_impl_my_error2;
typedef exc_impl<exc_my_error3> exc_impl_my_error3;
typedef exc_impl<exc_my_error1,exc_my_error2> // implements both
exc_impl_my_error12;
}
some_class::some_class(int i)
{
if(i < 0)
throw exc_impl_my_error3( EXC_INFO // passes '__FILE__', '__LINE__' etc.
, /* ... */ // more info on error
);
}
Looking back at this now, I think I could have made that exc_impl
class template derive from std::exception
(or any other class in the std lib exception hierarchy, passed as optional template parameter), since it never derives from any other exc_impl
instance. But back then this wasn't needed, so it never occurred to me.