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

C++中的虚拟继承

  •  3
  • Bruce  · 技术社区  · 15 年前

    我在读 Wikipedia 关于虚拟继承的文章。我看完了整篇文章,但我不能真正看完最后一段

    这是通过提供 有vtable的哺乳动物和有翼动物 指针(或“vPointer”),例如, 之间的内存偏移量 哺乳动物及其 动物部分直到运行时才知道。 因此蝙蝠变成 (vpointer,哺乳动物,vpointer,有翼动物,蝙蝠,动物)。 有两个vtable指针,每个指针一个 继承层次结构 继承动物。在这个例子中,一个 对于哺乳动物和有翼动物。 因此,对象大小 增加了两个指针,但现在 只有一只动物,没有 歧义。BAT类型的所有对象 将有相同的vPointer,但每个 BAT对象将包含其自己的唯一 动物对象。如果另一个班 继承自哺乳动物,如 松鼠,然后是 松鼠体内的哺乳动物 与中的vPointer不同 蝙蝠中的哺乳动物物体,尽管它们 在 特别是松鼠 对象的一部分大小相同 作为蝙蝠的一部分,因为 从哺乳动物到动物的距离 部分是相同的。vtables不是 真的一样,但都很重要 其中的信息(距离)是。

    有人能再解释一下吗?

    3 回复  |  直到 15 年前
        1
  •  6
  •   Matthieu M.    15 年前

    有时,您只需要查看一些代码/图表:)请注意,在标准中没有提到这个实现细节。

    首先,让我们看看如何在C++中实现方法:

    struct Base
    {
      void foo();
    };
    

    这类似于:

    struct Base {};
    
    void Base_foo(Base& b);
    

    实际上,当您查看调试器中的方法调用时,您经常会看到 this 参数作为第一个参数。有时称为隐式参数。

    现在,转到虚拟表。在C和C++中,可以有指针来运行。vtable本质上是指向函数的指针表:

    struct Base
    {
      int a;
    };
    
    void Base_set(Base& b, int i) { b.a = i; }
    int Base_get(Base const& b) { return b.a; }
    
    struct BaseVTable
    {
      typedef void (*setter_t)(Base&, int);
      typedef int (*getter_t)(Base const&);
    
      setter_t mSetter;
      getter_t mGetter;
    
      BaseVTable(setter_t s, getter_t g): mSetter(s), mGetter(g) {}
    } gBaseVTable(&Base_set, &Base_get);
    

    现在我可以做如下的事情:

    void func()
    {
      Base b;
      (*gBaseVTable.mSetter)(b, 3);
      std::cout << (*gBaseVTable.mGetter)(b) << std::endl; // print 3
    }
    

    现在,关于遗产。让我们创建另一个结构

    struct Derived: Base {}; // yeah, Base does not have a virtual destructor... shh
    
    void Derived_set(Derived& d, int i) { d.a = i+1; }
    
    struct DerivedBaseVTable
    {
      typedef void (*setter_t)(Derived&,int);
      typedef BaseVTable::getter_t getter_t;
    
      setter_t mSetter;
      getter_t mGetter;
    
      DerivedBaseVTable(setter_t s, getter_t g): mSetter(s), mGetter(g) {}
    } gDerivedBaseVTable(&Derived_set, &Base_get);
    

    用途:

    void func()
    {
      Derived d;
      (*gDerivedBaseVTable.mSetter)(d, 3);
      std::cout << (*gDerivedBaseVTable.mGetter)(d) << std::endl; // print 4
    }
    

    但是如何自动化呢?

    • 每个类只需要一个vtable实例,其中至少有一个虚拟函数
    • 类的每个实例都将包含一个指向vtable的指针作为其第一个属性(即使您自己不能真正访问它)。

    现在,在多重继承的情况下会发生什么?好吧,继承在内存布局方面非常类似于组合:

    |                                     Derived                                   |
    |                 BaseA                 |                 BaseB                 |
    | vpointer | field1 | field2 | padding? | vpointer | field1 | field2 | padding? |
    

    因此,将有两个虚拟表用于 MostDerived :一个用于更改方法的 BaseA 一个改变方法 BaseB .

    纯虚拟函数通常在相应的字段中表示为空指针(简单)。

    最后,建设和破坏:

    施工

    • 巴西亚 构造:首先初始化vPointer,然后初始化属性,然后执行构造函数的主体
    • 基布 构造:vPointer、属性、主体
    • Derived 构造:替换vPointer(两者)、属性、主体

    破坏

    • Derived is destructed :析构函数体,销毁属性,将基vPointer放回
    • 基布 已销毁:body,attributes
    • 巴西亚 已销毁:body,attributes

    我认为这是非常全面的,我会很高兴,如果一些c++大师在那里可以检讨这一点,并检查我没有犯任何愚蠢的错误。另外,如果有什么东西不见了,我很乐意加上去。

        2
  •  6
  •   mkluwe    15 年前

    我不能,真的。本节试图描述在使用虚拟方法表实现动态绑定(在多重继承的情况下)的C++实现中应该做些什么。

    如果你不是在做编译器,我的建议是:不要麻烦了。阅读关于继承、虚拟方法、多继承和虚拟继承的最喜欢的C++书籍。

    另外,C++标准(IIVC)不需要使用VTABLE,这是一个实现细节。所以说真的,不用麻烦了。

        3
  •  1
  •   Jørgen Fogh    15 年前

    正如mkluwe所建议的,vpointer并不是语言的一部分。但是,知道 实现技术可能是有用的,尤其是在像C++这样的低级语言中。

    如果你真的想学这个,我建议你 Inside the C++ Object Model 这就详细解释了这一点和其他很多事情。