C++中的虚拟继承

问题描述:

我在阅读关于虚拟继承的Wikipedia文章。我跟着整篇文章,但我不能真正遵循最后一段C++中的虚拟继承

这是通过提供 哺乳动物和WingedAnimal有虚表 指针自实施(或“vpointer”),例如, 内存之间的偏移 哺乳动物的开始和它的 动物部分直到运行时才知道。因此蝙蝠变成 (vpointer,Mammal,vpointer,WingedAnimal,Bat,Animal)。 有两个vtable指针,每个 继承层次结构,几乎 继承动物。在这个例子中,一个用于Mammal的 ,另一个用于WingedAnimal。 因此物体尺寸 增加了两个指针,但是现在的 只有一个动物而没有 含糊不清。 Bat 类型的所有对象将具有相同的vpointers,但每个蝙蝠对象都将包含其自己的唯一 动物对象。如果另一个类从哺乳动物 继承,如 松鼠,然后在松鼠在 哺乳动物对象的vpointer将 从在一个BAT中 哺乳动物对象的vpointer不同,尽管它们 仍然可以基本上在同一 特殊情况表示松鼠 部分物体具有与蝙蝠部分相同的尺寸 ,因为那么从哺乳动物到动物 部分的距离是相同的。这个vtable不是 真的是一样的,但是它们中的所有信息(距离)都是本质的 。

有人可以摆脱这一点更多的光。

有时候,你真的需要看一些代码/图表:)请注意,标准中没有提到这个实现细节。

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

struct Base 
{ 
    void foo(); 
}; 

这是类似的:

struct Base {}; 

void Base_foo(Base& b); 

而事实上,当你看到一个方法调用一个调试器中,您经常会将this参数看作第一个参数。它有时被称为隐式参数。

现在,上虚拟表。在C和C++中,可以使指针起作用。 V表基本上是函数指针表:

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 
} 

但如何实现自动化吗?

  • 你只需要具有至少一个虚函数
  • 类的每个实例都将包含一个指向虚函数表作为它的第一个属性(即使你真的不能访问每类虚函数表的一个实例它自己)

现在,在多重继承的情况下会发生什么?那么,继承是非常像的存储器布局术语组合物:

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

有将因此成为MostDerived 2个虚拟表:一个以改变从BaseA和一个方法来从BaseB改变方法。

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

最后,建筑和破坏:

施工

  • BaseA构造:首先vpointer被初始化,则属性,则该构造的主体被执行
  • BaseB被构造:vpointer,attributes,body
  • Derived构造:替换vpointers(两者),属性,主体

破坏

  • Derived is destructed:析构函数的身体,破坏属性,把基地vpointers回
  • BaseB被破坏:身体属性
  • BaseA被破坏:身体属性

我认为它非常全面,如果一些C++大师在那里可以检查一下并检查我哈哈,我会很高兴没有犯任何愚蠢的错误。另外,如果缺少某些东西,我很乐意添加它。

+0

真的很全面的答案!非常感谢! – Bruce 2010-07-05 13:33:17

+0

虚拟继承期间会发生什么?你如何解释蝙蝠在虚拟继承之后变成了(vpointer,Mammal,vpointer,WingedAnimal,Bat,Animal)? – Bruce 2010-07-05 15:02:38

+1

这里的“蝙蝠”是一个错误(会导致无限递归)而忽略它。在虚拟继承的情况下,虚拟继承的类只能被一个(通过派生类最多)实例化,但显然在“Mammal”和“WingedAnimal”部分之前。 – 2010-07-05 16:16:48

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

如果你没有编译器,我的建议是:不要打扰。阅读您最喜爱的继承,虚拟方法,多继承和虚拟继承的C++书籍。

另外,C++标准(IIRC)不要求使用vtable,它是一个实现细节。所以真的,不要打扰。

+0

我已经开始写一个解释vpointers的答案,但是你的答案可能会更好,除非他想读一整本书。 – 2010-07-04 17:08:45

+0

@Fogh:这件事一直困扰着我很长一段时间......我不介意看整本书 – Bruce 2010-07-04 17:14:25

正如mkluwe所说,vpointers并不是真正的语言组成部分。但是,了解 实现技术可能会很有用,特别是在像C++这样的低级语言中。

如果你真的想了解这一点,我会推荐Inside the C++ Object Model,这解释了这一点以及许多其他的细节。

+0

@Fogh:非常感谢 – Bruce 2010-07-04 17:15:02