4

关于这个有几个问题。但我仍然不清楚这一点。考虑这种多重继承。

class Base1 
{ 
   public: 
     Base1(); 
     virtual ~Base1(); 
     virtual void speakClearly(); 
     virtual Base1 *clone() const; 
   protected: 
     float data_Base1; 
}; 

class Base2 
{ 
   public: 
     Base2(); 
     virtual ~Base2(); 
     virtual void mumble(); 
     virtual Base2 *clone() const; 
   protected: 
     float data_Base2; 
}; 

class Derived : public Base1, public Base2 
{ 
   public: 
     Derived(); 
     virtual ~Derived(); 
     virtual Derived *clone() const; 
   protected: 
     float data_Derived; 
}; 

并考虑这两个陈述

Base1 *pbase1 = new Derived; 
Base2 *pbase2 = new Derived;

C++ 对象模型里面的书在其中一个优化中说明了这一点。

pbase1 子对象的 vtable 和 Derived 共享同一个 vtable。书中的确切短语

“Base 1 在最左边,它已经指向 Derived 类对象的开头”

这是怎么发生的?我知道如果我将继承顺序更改为

class Derived : public Base2, public Base1

但我不明白编译器是如何完成的。任何人都可以解释 pbase1 和派生如何共享同一个 v_table 吗?

4

2 回答 2

2

这都是非常定义的实现,但通常情况下,编译器将布局Derived

Base1: vptr
Base1: data
Base2: vptr
Base2: data
Derived: data

的地址Derived是整体的地址,因此与 的物理地址相同Base1。for 的 vtable 的初始部分Derived将与 的相同 Base1,并且 Base1: vptr 实际上将指向 的 vtable Derived

如果你在声明中反转Base1Base2,编译器将反转它们上面的角色。(通常。标准中没有任何内容可以阻止编译器按字母顺序排列基数,或将派生数据放在首位,但我从未听说过这样做的编译器。)

于 2013-10-02T08:27:05.260 回答
1

把一个类想象成在你声明的那些之前有一些“隐藏成员”,给出这样的布局:

  • 虚表指针
  • 第一基地
    • 它是 vtable 指针
    • 它是自己的成员
  • 第二基地
    • 它是 vtable 指针
    • 它是自己的成员
  • 派生成员

现在的问题是“指针应该指向哪里?”

由于所有函数都在最派生的对象中可用,因此您可以为以下内容创建“函数指针数组”:

  • dtor,
  • 说清楚
  • 克隆
  • 咕哝

此表对Derivedand的工作方式相同Base1(它只需要忽略最后一行),但对 没有Base2,必须看到

  • dtor
  • 克隆
  • 咕哝

与以前的索引不同(这就是它需要不同的原因)。

由于这个事实,没有必要区分 Derived 和 first-base 表实例(当然,我们可以做同样的推理,从最后一个 base 开始并区分前面的......但是我们必须从某个地方开始)

因此,编译器将“压缩”实现,方法是只创建一个表,其中包含第一个基本虚函数加上所有其他虚函数,以及其他基本虚函数的其他不同表,而不是初始化所有指向最派生实现的指针虚拟职能。

布局将是

  1. 派生的虚表
  2. Base1 成员
  3. Base2 与 Derived vtable
  4. Base2 成员
  5. 派生成员

现在,new Derived将创建该草图,并且:

  • 如果给定Derived*将使其指向 1。
  • 如果给定Base1*将使其指向 1。(就像 Derived 一样,但“更短”)
  • 如果给定Base2*将使其指向 3。
于 2013-10-02T08:41:35.630 回答