Why the result is "B", I thought that it should hit first inherited class ("A")? When I ran it with class B that do not inherit anything from class A it hits first catch block, but I don't know reason for behavior like this in code below:
#include <iostream>
#include <exception>
using namespace std;
class A {};
class B : public A{};
class C : public A, public B {};
int main() {
try {
throw C();
}
catch (A a) {
cout << "A" << endl;
}
catch (B b) {
cout << "B" << endl;
}
catch (C c) {
cout << "C" << endl;
}
}
Your classes are some variant of the deadly diamond of death case of multiple inheritance: the base class A is twice a base class of C: once directly, and once indirectly via B:
A
|\
| \
| B
| /
|/
C
The consequence of this kind of inheritance graph is that your C object has two different A
sub-objects. I name them in the graph for convenience:
a1:A a2:A
| |
| |
| b:B
| /
| /
c:C
As you see, if you mention the A sub-object, there is an ambiguity: is it a1
or a2
, whereas for B
there is no ambiguity. And this impacts the matching the exception handling. Because the standard's rules are as follows:
[except.handle]/3: A handler is a match for an exception object of type E if
— The handler is of type cv T or cv T& and E and T are the same type (ignoring the top-level cv-qualifiers), or
— the handler is of type cv T or cv T& and T is an unambiguous public base class of E, or
— ...
When you throw C
, first the catch (A a)
is tested: A is not the same type than C. And A is non an unambiguous public base class either. Then the next catch (B b)
is tested: B is not the same type than C. But it's an unambiguous public base class of it. Match! And therefore your result.
Making A
a virtual base of both B
and C
as suggested in the other answer, ensures that there is only one unique and unambiguous A sub-object in C. This is why it works.
Virtual inheritance is not a silver bullet. As soon as you have non-default constructors, you must explicit the virtual constructor systematically. Moreover, you must use the virtual inheritance wherever A is a subclass. Last but not least, virtual inheritance is sometimes just not suitable for the needs (for example if two independent strands are needed).
In this last situation, you could also disambiguate by introducing an intermediary class:
class AA : public A {};
class C : public AA, public B {};
and then catch (AA& a)
instead of catching A
. This is stupid simple, but it removes the ambiguity, whereas there are still two different A sub-objects (again: only if you need them). (online demo)
Recommendation: To get a full understanding of the situation, I recommend that you try to update your example and add in each class some members that you'd initialize with a non-default constructor. ANd yes, catch the exception by reference.