Consider the following C++ code:
class A
{
public:
int i;
}
class B: virtual public A{};
class C: virtual public A{};
class D: public B, public C{};
int main()
{
D obj;
obj.i = 0;
return 0;
}
The code is the classic example of virtual inheritance handling diamond structure. My concern is why we used the word virtual
with class B
and C
? The keyword has no effect on the classes. Furthermore, when I am writing class B
or C
, I do not think about using it later as base class of some derived class and even I may not know that classes B
or C
have siblings. So, I have to actually add the keyword latter coming back to them when I will implement class D
. This seems counterintuitive to me. Why I have to disturb class B
or C
when they have no concern about the diamond structure evolved here. To me, the following is logical:
class A
{
public:
int i;
}
class B: public A{};
class C: public A{};
class D: virtual public B, virtual public C{};
Here I have added the keyword virtual
when I noticed that a diamond structure is evolving there.
But C++ is not designed in that way. Can anyone tell what the motive or imagination behind such design was?
C++ wanted to have a stable ABI and a minimal runtime. which means that B
and C
and D
can exist in 3 different shared libraries (DLLs).
whenever you access a virtual base, the compiler has to emit a "virtual call like" code to access this base because it doesn't know where it is, only the most-derived type knows where this virtual base is, because only the most derived type knows all the inherited classes.
class B: virtual public A{
void increment()
{
i++;
}
};
without virtual inheritance this function is simply incrementing a member.
but with virtual inheritance this function has to make a virtual function-like call to fetch the position of i
then increment it, a rough simulation of virtual inheritance is as follows.
class B_base {
virtual A* get_A() = 0;
void increment()
{
A* base_A = get_A(); // virtual function
base_A->i++;
}
};
class B_actual: public A, public B_base
{
A* get_A() override { return this; }
};
class D_actual: public A, public B_base, public C_base
{
A* get_A() override { return this; }
};
when you declare D
, the compiler cannot modify B
and C
to fix their layout and functions, it may not even be in the same library. you are required to plan ahead when declaring B
and C
and have them virtually inherit A
if you know D
is going to inherit both of them, which ultimately means you need to plan your inheritance hierarchies ahead of time.