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++大师在那里可以检查一下并检查我哈哈,我会很高兴没有犯任何愚蠢的错误。另外,如果缺少某些东西,我很乐意添加它。
我不能,真的。本节试图描述在使用虚拟方法表提供动态绑定(在多重继承的情况下)的C++实现中应该做些什么。
如果你没有编译器,我的建议是:不要打扰。阅读您最喜爱的继承,虚拟方法,多继承和虚拟继承的C++书籍。
另外,C++标准(IIRC)不要求使用vtable,它是一个实现细节。所以真的,不要打扰。
我已经开始写一个解释vpointers的答案,但是你的答案可能会更好,除非他想读一整本书。 – 2010-07-04 17:08:45
@Fogh:这件事一直困扰着我很长一段时间......我不介意看整本书 – Bruce 2010-07-04 17:14:25
正如mkluwe所说,vpointers并不是真正的语言组成部分。但是,了解 实现技术可能会很有用,特别是在像C++这样的低级语言中。
如果你真的想了解这一点,我会推荐Inside the C++ Object Model,这解释了这一点以及许多其他的细节。
@Fogh:非常感谢 – Bruce 2010-07-04 17:15:02
真的很全面的答案!非常感谢! – Bruce 2010-07-05 13:33:17
虚拟继承期间会发生什么?你如何解释蝙蝠在虚拟继承之后变成了(vpointer,Mammal,vpointer,WingedAnimal,Bat,Animal)? – Bruce 2010-07-05 15:02:38
这里的“蝙蝠”是一个错误(会导致无限递归)而忽略它。在虚拟继承的情况下,虚拟继承的类只能被一个(通过派生类最多)实例化,但显然在“Mammal”和“WingedAnimal”部分之前。 – 2010-07-05 16:16:48