【问题标题】:Diamond shaped polymorphic Inheritance: sizeof Most derived Class菱形多态继承:sizeof 大部分派生类
【发布时间】:2011-07-27 01:21:17
【问题描述】:

我知道菱形继承会导致歧义,可以通过virtual Base Classes 使用继承来避免这种情况,问题不在于它。当类是多态的时,问题是关于菱形层次结构中最派生类的大小。这是一个示例代码和示例输出:

#include<iostream>

using namespace std;

class Base
{
    public:
        virtual void doSomething(){}  
};

class Derived1:public virtual Base
{
    public:
       virtual void doSomething(){}
};

class Derived2:public virtual Base
{
    public:
       virtual void doSomething(){}
};

class Derived3:public Derived1,public Derived2
{
    public:
       virtual void doSomething(){}
};

int main()
{
    Base obj;
    Derived1 objDerived1;
    Derived2 objDerived2;
    Derived3 objDerived3;

    cout<<"\n Size of Base: "<<sizeof(obj);
    cout<<"\n Size of Derived1: "<<sizeof(objDerived1);
    cout<<"\n Size of Derived2: "<<sizeof(objDerived2);
    cout<<"\n Size of Derived3: "<<sizeof(objDerived3);

    return 0;
}

我得到的输出是:

 Size of Base: 4
 Size of Derived1: 4
 Size of Derived2: 4
 Size of Derived3: 8

据我了解,Base 包含一个虚拟成员函数,因此,
sizeof Base = vptr 的大小 = 4 在这个环境中

Derived1Derived2 类的情况类似。

这是我与上述情况相关的问题:
Derived3 类对象的大小如何,这是否意味着 Derived3 类有 2 个 vptr?
Derived3 类如何与这 2 个 vptr 一起工作,关于它使用的机制有什么想法吗?
类的大小作为编译器的实现细节留下而不是由标准定义(因为虚拟机制本身是编译器的实现细节)?

【问题讨论】:

  • 关于标准问题。是的,如何实现虚拟方法的机制是一个实现细节,没有具体说明。是的,sizeof 的实际结果也是一个实现细节,它主要取决于指针大小,如果你在 64 位平台上,你会看到8/8/8/16

标签: c++ sizeof virtual-inheritance diamond-problem memory-layout


【解决方案1】:

是的,Derived3 有两个 vtable 指针。如果您按值访问它,它使用Derived3 版本,或者从父级选择一个函数,或者如果它无法决定,则表示它是模棱两可的。

对于一个孩子,它使用对应于被多态使用的父 1/2 的 vtable。

请注意,您没有正确使用虚拟继承:我认为 Derived1 和 2 应该虚拟继承自 Basesizeof(Derived3) 似乎仍然是 8,因为它仍然有两个可能的父母可以被视为 Derived3。当您转换为其中一个父级时,编译器实际上会调整对象指针以具有正确的 vtable。

另外我应该指出,任何与 vtable 相关的内容都是特定于实现的,因为标准中甚至没有提及 vtable。

【讨论】:

  • 谢谢,我更正了实际上继承自 Base 的 Derived1 和 Derived2 的错字。 O/P 还是一样的。
  • 我刚刚添加了更多数据成员here。好像sizeof(Dervied3) = sizeof(Base::base_data) + sizeof(Derived1::d1_data) + sizeof(Derived2::d2_data) + sizeof(Derived3::d3_data) + 3 * 8。它是x86-64,所以每个指针都是8字节。你说Dervied3 有两个虚拟指针。那么额外的 8 字节是多少呢?
【解决方案2】:

我认为您想知道一些完全特定于实现的东西。 你不应该假设类的大小。

编辑:虽然好奇是一种经过验证的品质 ;-)

【讨论】:

    【解决方案3】:

    对您的代码的一个小修复:虚拟应该在derived2 和derived 3 的定义中才能工作。

    http://www.parashift.com/c++-faq-lite/multiple-inheritance.html#faq-25.9

    【讨论】:

      【解决方案4】:

      考虑一个稍微不同的情况:

      struct B { virtual void f(); };
      struct L : virtual B { virtual void g(); };
      struct R : virtual B { virtual void h(); };
      struct D : L, R {};
      

      在典型的实现中,L::g 将位于相同的位置(比如在 索引 0) 在 L 的 vtable 中作为 R:h 在 R 的 vtable 中。现在考虑会发生什么 给出以下代码:

      D* pd = new D;
      L* pl = pd;
      R* pr = pd;
      pl->g();
      pr->h();
      

      在最后两行中,编译器将生成代码来查找 函数在 vtable 中相同位置的地址。所以 通过 pl 访问的 vtable 不能与 vtable 相同(或前缀 一)通过公关访问。因此,完整的对象至少需要 两个vptr,指向两个不同的vtable。

      【讨论】:

      • 实际上在典型实现中f 可能会在虚拟表中出现在gh 之前,因此在将LR 多态地用作@ 时无需调整987654328@(即L 表的布局将是B 表布局的超集,因此它的B 视图符合B 尝试)。
      • @Matthieu M. 是的。在许多实现中,vtable 会以一个或多个特殊指针开始,例如指向 type_info 信息,然后是基类中的虚函数,依次是类中未被覆盖的虚函数一个基地。然而,事实仍然是,L 的 vtable 的 g 与 R 的 vtable 有 h 的位置相同,因此同一个 vtable 不能同时用于 D 中的 L 和 R。
      • 确切地说,在很多实现中。构建更智能的 vtable 需要整个程序分析/链接时间优化。
      • 这是一个有趣/令人信服的解释。谢谢!
      • @MatthieuM。 "L 表的布局将是一个超集" L 表的不变量 将是...
      猜你喜欢
      • 2020-10-26
      • 1970-01-01
      • 1970-01-01
      • 2020-02-01
      • 1970-01-01
      • 2018-11-30
      • 2012-10-16
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多