代码之家  ›  专栏  ›  技术社区  ›  jchl

在复杂的多重继承层次结构中,“virtual”关键字在哪里是必需的?

  •  11
  • jchl  · 技术社区  · 14 年前

    我理解C++虚拟继承的基础知识。但是,我不知道我到底需要在哪里使用 virtual 具有复杂类层次结构的关键字。例如,假设我有以下类:

                A
               / \
              B   C
             / \ / \
            D   E   F
             \ / \ /
              G   H
               \ /
                I
    

    如果我想确保这些类在任何子类中都不会出现一次以上,那么需要标记哪些基类 事实上的 ? 所有人?或者仅仅在那些直接从一个可能有多个实例的类(即B、C、D、e和F;以及G和H(但仅与基类E有关,而与基类D和F无关)?

    7 回复  |  直到 14 年前
        1
  •  8
  •   Didier Trosset    14 年前

    您必须指定 virtual 从A、B、C和E类(位于菱形顶部)继承时的继承。

    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;
    
        2
  •  25
  •   Glorfindel Luis E. Prado    5 年前

    我玩了一个程序,可以帮助你研究复杂的虚拟基地。它打印下的类层次结构 I 作为一个适合作图的有向图( http://www.graphviz.org/ ). 每个实例都有一个计数器,可以帮助您理解施工顺序。程序如下:

    #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");
    }
    

    g++ base.cc ; ./a.out >h.dot ; dot -Tpng -o o.png h.dot ; display o.png ),我得到了典型的非虚拟基树: alt text

    添加足够的虚拟性。。。

    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) };
    

    alt text

    但如果你把所有的基地都虚拟化了:

    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) };
    

    你得到一颗钻石 以不同的初始化顺序 :

    alt text

    玩得高兴!

        3
  •  2
  •   Puppy    14 年前

    实际上,我会说,B和C:虚A,G和H:虚E,E:虚B和C。所有其他继承链接都可以是普通继承。不过,这个怪物要花60年才能打一个虚拟电话。

        4
  •  1
  •   AnT stands with Russia    14 年前

    如果要确保层次结构中顶层类的对象( I 在您的例子中)只包含每个父类的一个子对象,您必须找到层次结构中具有 多个超类 上这些课 事实上的 超类的基础。就这样。

    A , B , C E 每次从这个层次结构中继承时都必须成为虚拟基类。

    班级 D F , G H 不必成为虚拟基类。

        5
  •  0
  •   Klaim    14 年前

    如果您希望每种类型的每个实例只有一个“物理”实例(只有一个A,只有一个B等等),那么每次使用继承时您只需要使用虚拟继承。

    如果需要其中一个类型的单独实例,请使用普通继承。

        6
  •  0
  •   Matthieu M.    14 年前

    编辑 :我认为A是最派生的类;)

    @路德的回答很酷,但又回到了最初的问题:

    你需要使用 virtual 继承从继承层次结构中至少有一个其他类继承的任何类继承时(在路德的图中,它意味着至少有两个箭头指向该类)。

    在这之前没必要 D , F G H I 目前)。

    但是,如果您事先不知道另一个类是否将从基类继承,则可以添加 事实上的 作为预防措施。例如,建议 Exception std::exception 只有自力更生。

    正如Luther所指出的,它修改了实例化顺序(并且对性能有轻微的影响),但是我认为任何依赖于构造顺序的设计从一开始就是错误的。作为一种精确性:您仍然可以保证基类在派生类的任何属性之前初始化,因此在执行派生的构造函数体之前初始化。

        7
  •  0
  •   user5497885 user5497885    9 年前

    一般来说,如果可以避免虚拟类,可以用一些模板替换,或者尝试以某种方式解耦。