【问题标题】:C++ virtual table layout of MI(multiple inheritance)MI的C++虚拟表布局(多重继承)
【发布时间】:2013-04-10 09:00:08
【问题描述】:

看下面的C++代码

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;  
}; 

《C++对象模型内幕》4.2说Base1、Base2和Derived类的虚表布局是这样的:

我的问题是:

Derived 类的 Base1 子对象的虚拟表包含Base2::mumble。为什么?我知道 Derived 类与 Base1 共享了这个虚拟表,所以我认为 Base2 的功能不应该出现在这里。有人能告诉我为什么吗?谢谢。

【问题讨论】:

  • Derived vtable 的Base1 之后添加额外的条目并没有什么坏处。可以这样做是为了提高效率。给定一个Derived* 指针,通过Base1/Derived vtable 调用虚函数比通过Base2 vtable 更便宜。
  • 注意:事情的呈现方式似乎搞砸了,在 Itanium ABI 中,_vptr 成员实际上是first;同样Base1Derivedfirst 成员。
  • @MatthieuM。在我见过的所有编译器中,_vptr(伪)成员一直是类中的第一件事,但标准显然允许它在任何地方使用。
  • @JamesKanze:是的,这就是为什么我要准确地说我正在谈论的 ABI(这也是我唯一稍微熟悉的 ABI);但在我看来,要使优化相关,您希望尽可能轻松地找到Base1(以及_vptr)的地址;理想情况下不涉及算术。
  • @MatthieuM。您希望尽可能轻松地找到所有_vptr 的地址:-)。说真的,对于英特尔来说,有一个论点是将_vptr 放在第一个基类的末尾,并维护一个指向 it 的指针作为指向对象的指针。 (没有规则,或者至少以前没有,Derived* 必须指向对象的第一个字节。)英特尔有一种寻址模式,其中基指针的偏移量可能是单个字节,在-128...127 的范围内。把指向对象的指针放到对象的中间意味着可以多用这个。

标签: c++ oop


【解决方案1】:

嗯,首先,我要提醒大家,实现多态性的解决方案的设计是标准之外的 ABI 决策。例如,MSVC 和 Itanium ABI(之后是 gcc、clang、icc 等)有不同的实现方式。

除此之外,我认为这是对查找的优化。

当您有一个Derived 对象(或其后代之一)并查找mumble 成员时,您不需要实际找出Base2 子对象,而是可以直接从Base1 子对象执行操作(其地址与Derived 子对象一致,因此不涉及算术)。

【讨论】:

  • 更根本的是,编译器在Base1的函数后面添加了Derived的函数,这样就可以同时使用同一个vtable。而mumble 也是Derived 的函数(通过继承),而不仅仅是Base2 的函数。这是一种“优化”,但它是基于语言基础的优化。我无法想象任何编译器不这样做。
  • 真的是有用的优化吗?要调用 mumble,您仍然需要为其this 指针找到Base2 子对象,因此您没有保存任何工作。唯一可能的改进是,如果您经常检查 mumble 的地址而不调用它,或者如果它提高了第一个子对象 vtable 上的缓存命中率。
  • @Useless:我必须承认我不确定它是否有效;但是它确实消除了数据依赖性。您现在可以并行计算mumble 的地址和Base2 子对象的地址。因此,CPU 可以计算mumble,开始从内存中加载代码,并并行计算Base2 子对象。
  • 我想这只是一个错字我猜 Derived 的 vtable 实际上与代表 Base1 和 Base2 vtables 的 2 个段连续,所以从 Derived 的角度调用任何虚函数(如果我们知道对象确实是派生的)需要相同的指针算术(从整个派生虚表的开头偏移)我还发现另一个错字: Derived::~clone() 而不是 Derived::clone() (就在有问题的行之前)
  • @user396672:这不是一个错误。 DerivedBase1 有不同的 v-tables,但是 Base1 中的 v-ptr 可以指向 Derived 的表,因为表 Base1Derived 的前缀(和 @987654346 @ 只会使用它知道的前缀)。
【解决方案2】:

在运行时当你得到:

    Base2 b2;
    Base1* b1_ptr = (Base1*)&b2;
    b1_ptr->mumble();    // will call Base2::mumble(), this is the reason.

然后需要调用 Base2::mumble() ! 注意 mumble() 是唯一在层次结构中被覆盖的虚拟方法。 (甚至,您可能认为 clone() 也被覆盖了,但是它在类之间返回不同的类型,那么它是另一个签名)。

【讨论】:

  • 我很困惑,这不会是编译器错误,因为 Base1 没有名为 mumble 的函数吗?
猜你喜欢
  • 2012-07-21
  • 2017-03-31
  • 2015-09-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-13
  • 2014-11-06
相关资源
最近更新 更多