Where is the “virtual” keyword necessary in a complex multiple inheritance hierarchy?

后端 未结 7 1691
死守一世寂寞
死守一世寂寞 2021-01-30 19:00

I understand the basics of C++ virtual inheritance. However, I\'m confused about where exactly I need to use the virtual keyword with a complex class hierarchy. F

相关标签:
7条回答
  • 2021-01-30 19:20

    If you want to make sure that an object of the top class in the hierarchy (I in your case) contains exactly one subobject of each parent class, you have to find all classes in your hierarchy that have more than one superclass and make these classes virtual bases of their superclasses. That's it.

    In your case classes A, B, C and E have to become virtual base classes every time you inherit from them in this hierarchy.

    Classes D, F, G and H don't have to become virtual base classes.

    0 讨论(0)
  • 2021-01-30 19:21

    My personal suggestion would be to start at B and C : virtual A, and then keep adding until the compiler stops complaining.

    In reality, I'd say that B and C : virtual A, G and H : virtual E, and E : virtual B and C. All the other inheritance links can be normal inheritance. This monstrosity would take like six decades to make a virtual call, though.

    0 讨论(0)
  • 2021-01-30 19:21

    Edited: I thought A was the most derived class ;)

    @Luther's answer really cool, but back to the original question:

    You NEED to use virtual inheritance when inheriting from any class from which at least one other class inherits in the inheritance hierarchy (in Luther's diagrams it means at least two arrows point to the class).

    Here it's unnecessary before D, F, G and H because only one class derives from them (and none derives from I at the moment).

    However, if you don't know beforehand whether or not another class will inherit from your base class, you can add in virtual as a precaution. For example it's recommended for an Exception class to inherit virtually from std::exception by no other than Stroustrup himself.

    As Luther has noted, it modifies the instantiation order (and has a slight effect on performances) but I would consider any design relying on the construction order to be wrong to begin with. And just as a precision: you still have the guarantees that base classes are initialized before any attribute of the derived class, and therefore before the execution of the derived's constructor body.

    0 讨论(0)
  • 2021-01-30 19:27

    You have to specify virtual inheritance when inheriting from any of A, B, C, and E classes (that are at the top of a diamond).

    class A;
    class B: virtual A;
    class C: virtual A;
    class D: virtual B;
    class E: virtual B, virtual C;
    class F: virtual C;
    class G:         D, virtual E;
    class H: virtual E,         F;
    class I:         G,         H;
    
    0 讨论(0)
  • 2021-01-30 19:36

    On thing to keep in mind is C++ keeps a table of the inheritance. The more you add virtual classes, the longer will be compilation time (linkage) and the heavier would be the runtime.

    In general, if one can avoid virtual class, you can replace by some templates or try to decouple in some way.

    0 讨论(0)
  • 2021-01-30 19:38

    I toyed a program together which could help you to study the intricacies of virtual bases. It prints the class hierarchy under I as a digraph suitable for graphiviz ( http://www.graphviz.org/ ). There's a counter for each instance which helps you to understand the construction order as well. Here's the programm:

    #include <stdio.h>
    int counter=0; 
    
    
    
    #define CONN2(N,X,Y)\
        int id; N() { id=counter++; }\
        void conn() \
        {\
            printf("%s_%d->%s_%d\n",#N,this->id,#X,((X*)this)->id); \
            printf("%s_%d->%s_%d\n",#N,this->id,#Y,((Y*)this)->id); \
            X::conn(); \
            Y::conn();\
        }
    #define CONN1(N,X)\
        int id; N() { id=counter++; }\
        void conn() \
        {\
            printf("%s_%d->%s_%d\n",#N,this->id,#X,((X*)this)->id); \
            X::conn(); \
        }
    
    struct A { int id; A() { id=counter++; } void conn() {} };
    struct B : A { CONN1(B,A) };
    struct C : A { CONN1(C,A)  };
    struct D : B { CONN1(D,B) };
    struct E : B,C { CONN2(E,B,C) };
    struct F : C { CONN1(F,C) };
    struct G : D,E { CONN2(G,D,E) };
    struct H : E,F { CONN2(H,E,F) };
    struct I : G,H { CONN2(I,G,H) };
    int main()
    {
        printf("digraph inh {\n");
        I i; 
        i.conn(); 
        printf("}\n");
    }
    

    If I run this (g++ base.cc ; ./a.out >h.dot ; dot -Tpng -o o.png h.dot ; display o.png), I get the typical non-virtual base tree:

    Adding enough virtualness...

    struct B : virtual A { CONN1(B,A) };
    struct C : virtual A { CONN1(C,A)  };
    struct D : virtual B { CONN1(D,B) };
    struct E : virtual B, virtual C { CONN2(E,B,C) };
    struct F : virtual C { CONN1(F,C) };
    struct G : D, virtual E { CONN2(G,D,E) };
    struct H : virtual E,F { CONN2(H,E,F) };
    struct I : G,H { CONN2(I,G,H) };
    

    ..results in the diamond shape (look at the numbers to learn the construction order!!)

    But if you make all bases virtual:

    struct A { int id; A() { id=counter++; } void conn() {} };
    struct B : virtual A { CONN1(B,A) };
    struct C : virtual A { CONN1(C,A)  };
    struct D : virtual B { CONN1(D,B) };
    struct E : virtual B, virtual C { CONN2(E,B,C) };
    struct F : virtual C { CONN1(F,C) };
    struct G : virtual D, virtual E { CONN2(G,D,E) };
    struct H : virtual E, virtual F { CONN2(H,E,F) };
    struct I : virtual G,virtual H { CONN2(I,G,H) };
    

    You get a diamond with a different initialization order:

    Have fun!

    0 讨论(0)
提交回复
热议问题