菱形多态继承:sizeof大多数派生类

时间:2011-03-31 16:17:01

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

据我所知,Diamond形状的继承会导致歧义,可以通过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的大小

情况类似Derived1&amp; Derived2课程。

以下是与上述情况有关的问题:
Derived3类对象的大小如何,这是否意味着Derived3类有2个vptr?
Derived3类如何使用这两个vptr,有关它使用的机制的任何想法?   sizeof类保留为编译器和实现的实现细节。标准没有定义(因为虚拟机制本身就是编译器的实现细节)?

4 个答案:

答案 0 :(得分:4)

是的,Derived3有两个vtable指针。如果您按值访问它,它会使用Derived3版本,或从父项中选择一个函数,或者表示如果它无法决定它是不明确的。

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

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

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

答案 1 :(得分:3)

对您的代码的一个小修复:虚拟应该在derived2和derived3的定义中,以便工作。

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

答案 2 :(得分:1)

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

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

答案 3 :(得分:1)

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

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's vtable中作为R:h在R的vtable中。现在考虑会发生什么 给出以下代码:

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

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