Why does this work?
#include <exception>
#include <iostream>
#include <stdexcept>
#include <boost/exception/all.hpp>
struct foo_error : virtual boost::exception, public std::runtime_error
{
explicit foo_error(const char* what)
: std::runtime_error(what)
{ }
explicit foo_error(const std::string& what)
: std::runtime_error(what)
{ }
};
struct bar_error : virtual boost::exception, public std::runtime_error
{
explicit bar_error(const char* what)
: std::runtime_error(what)
{ }
explicit bar_error(const std::string& what)
: std::runtime_error(what)
{ }
};
struct abc_error : virtual foo_error, virtual bar_error
{
explicit abc_error(const char* what)
: foo_error(what), bar_error(what)
{ }
explicit abc_error(const std::string& what)
: foo_error(what), bar_error(what)
{ }
};
static void abc()
{
throw abc_error("abc error");
}
int main()
{
try
{
abc();
}
catch (const std::exception& e)
{
std::cerr << e.what();
}
}
I thought this shouldn't compile due to the ambiguous conversion from abc_error
to std::exception
. What am I missing? I came up with the inheritance diagram, and I can't really figure out why this code works (the arrows denote virtual inheritance and the lines denote non-virtual inheritance).
std::exception std::exception
+ +
| |
| |
+ +
std::runtime_error std::runtime_error
+ +
| |
| +-->boost::exception<-+ |
+ | | +
foo_error+<-----+ +--->+bar_error
| |
| |
| |
+abc_error+
It looks like abc_error
includes two instances of std::exception
so the catch
(or so I thought) shouldn't be able to cast abc_error
to std::exception
. Or should it?
UPDATE
I can't answer my own questions at the moment, so I'm going to continue here. I've narrowed the problem down to:
struct NonVirtualBaseBase { };
struct NonVirtualBase : NonVirtualBaseBase { };
struct VirtualBase { };
struct A : virtual VirtualBase, NonVirtualBase { };
struct B : virtual VirtualBase, NonVirtualBase { };
struct C : A, B { };
int main()
{
try
{
throw C();
}
catch (const VirtualBase& e)
{
return 1;
}
return 0;
}
The sample above works as expected and is a perfectly fine piece of code. It crashes if I replace catch (const VirtualBase& e)
with catch (const NonVirtualBase& e)
which I think is sane and makes sense. But it also works if I replace the same line with catch (const NonVirtualBaseBase& e)
which to me seems odd and wrong. A compiler bug?
UPDATE
As pointed out by the OP, this explanation doesn't quite cut it, since std::exception
is not derived from using virtual inheritance. I believe the answer to be that this is actually undefined behaviour and simply not caught at compile time because there is no need for the throw
and catch
to know of each other and warn if they are incompatible.
END UPDATE
The answer is that this hierarchy uses*virtual inheritance* to derive from boost::exception
.
Since both foo_error
and bar_error
uses virtual inheritance to inherit from boost::exception
, there will only be a single boost::exception
base which is shared between both the foo_error
and bar_error
sub-objects of an abc_error
.
When you specify virtual
before an entry in the list of base classes, this means all occurences of this class as a virtual base class in the most derived object will actually refer to the same instance. It is used specifically to avoid ambiguity in this type of design.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With