C++中的虚拟继承

Bru*_*uce 3 c++ virtual inheritance

我正在阅读关于虚拟继承的维基百科文章.我跟着整篇文章,但我真的不能遵循最后一段

这是通过自,例如,提供哺乳动物和WingedAnimal用虚表指针(或"vpointer")来实现,存储器的哺乳动物及其动物部分的开始之间的偏移是未知的,直到运行时.因此蝙蝠成为(vpointer,哺乳动物,vpointer,WingedAnimal,蝙蝠,动物).有两个vtable指针,每个继承层次结构一个,实际上继承了Animal.在这个例子中,一个用于哺乳动物,一个用于WingedAnimal.因此,对象大小增加了两个指针,但现在只有一个Animal,没有歧义.Bat类型的所有对象都具有相同的vpointers,但每个Bat对象将包含其自己的唯一Animal对象.如果另一个类继承自Mammal,例如Squirrel,然后在松鼠的哺乳动物对象的vpointer将从在一个蝙蝠的哺乳动物对象的vpointer不同,尽管它们仍然可以在该对象的松鼠部分具有的大小与蝙蝠相同的特殊情况下基本上相同的因为那时从哺乳动物到动物部分的距离是相同的.vtable并不是真的相同,但它们中的所有基本信息(距离)都是.

有人可以对此有所了解.

mkl*_*uwe 6

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

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

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


Mat*_* M. 6

有时,您只需要查看一些代码/图表:)请注意,标准中没有提及此实现细节.

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

struct Base
{
  void foo();
};
Run Code Online (Sandbox Code Playgroud)

这类似于:

struct Base {};

void Base_foo(Base& b);
Run Code Online (Sandbox Code Playgroud)

实际上,当您在调试器中查看方法调用时,您通常会将该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);
Run Code Online (Sandbox Code Playgroud)

现在我可以这样做:

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

现在,继承.让我们创建另一个结构

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);
Run Code Online (Sandbox Code Playgroud)

使用方法:

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

但是如何自动化?

  • 你只需要一个具有至少一个虚函数的每个类的vtable实例
  • 该类的每个实例都将包含一个指向vtable的指针作为其第一个属性(即使您无法自己真正访问它)

现在,在多继承的情况下会发生什么?那么,继承与内存布局方面的构成非常相似:

|                                     Derived                                   |
|                 BaseA                 |                 BaseB                 |
| vpointer | field1 | field2 | padding? | vpointer | field1 | field2 | padding? |
Run Code Online (Sandbox Code Playgroud)

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

纯虚函数通常在相应字段中表示为空指针(简称).

最后,建设和破坏:

施工

  • BaseA 构造:首先初始化vpointer,然后是属性,然后执行构造函数的主体
  • BaseB 构造:vpointer,属性,身体
  • Derived 构造:替换vpointers(两者),属性,身体

毁坏

  • Derived is destructed:析构函数的主体,破坏属性,将基本的vpointers放回去
  • BaseB 被破坏:身体,属性
  • BaseA 被破坏:身体,属性

我认为它非常全面,如果有一些C++专家可以对此进行审核并检查我没有犯下任何愚蠢的错误,我会很高兴.此外,如果缺少某些东西,我很乐意添加它.