c++virtual-inheritance

Why the keyword "virtual" is used in classes on whose it has no effect?


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?


Solution

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