虚拟继承构造函数顺序

时间:2015-06-01 05:44:29

标签: c++ inheritance virtual-inheritance

我试图更好地理解虚拟继承的概念,以及它的危险性。

我在另一篇文章(Why is Default constructor called in virtual inheritance?)中读到它(=虚拟继承)改变了构造函数调用的顺序("祖母"首先被调用,而没有虚拟继承它没有' ; t)的

所以我尝试了以下内容,看看我有了这个想法(VS2013):

#define tracefunc printf(__FUNCTION__); printf("\r\n")
struct A
{
    A(){ tracefunc; }

};

struct B1 : public A
{
    B1(){ tracefunc; };
};

struct B2 : virtual public A
{
    B2() { tracefunc; };
};

struct C1 : public B1
{
    C1() { tracefunc; };
};

struct C2 : virtual public B2
{
    C2() { tracefunc; };
};

int _tmain(int argc, _TCHAR* argv[])
{
    A* pa1 = new C1();
    A* pa2 = new C2();
}

输出结果为:

A::A
B1::B1
C1::C1
A::A
B2::B2
C2::C2

这不是我的预期(我预计2个班级的顺序会有所不同)。

我错过了什么?有人可以解释或指导我更好地解释它吗?

谢谢!

3 个答案:

答案 0 :(得分:2)

在您的示例中,您的输出是预期的。 Virtual inheritance在实例中发挥作用,当你有一个具有多重继承的类时,其父类也从相同的类/类型继承(即"钻石问题")。在您的示例中,您的类可能设置为虚拟继承(如果代码中的其他位置需要),但它们不一定是“虚拟继承”。基于您的示例,因为所有派生类(B1/B2/C1/C2)都不会直接从A继承。

为了扩展,我已经调整了你的例子来解释一下:

#include <cstdio>

#define tracefunc printf(__FUNCTION__); printf("\r\n")
struct A
{
    A() { tracefunc; }
    virtual void write() { tracefunc; }
    virtual void read() { tracefunc; }
};

struct B1 : public A
{
    B1() { tracefunc; };
    void read(){ tracefunc; }
};

struct C1 : public A
{
    C1() { tracefunc; };
    void write(){ tracefunc; }
};

struct B2 : virtual public A
{
    B2() { tracefunc; };
    void read(){ tracefunc; }
};

struct C2 : virtual public A
{
    C2() { tracefunc; };
    void write(){ tracefunc; }
};

// Z1 inherits from B1 and C1, both of which inherit from A; when a call is made to any
// of the base function (i.e. A::read or A::write) from the derived class, the call is
// ambiguous since B1 and C1 both have a 'copy' (i.e. vtable) for the A parent class.
struct Z1 : public B1, public C1
{
    Z1() { tracefunc; }
};

// Z2 inherits from B2 and C2, both of which inherit from A virtually; note that Z2 doesn't
// need to inherit virtually from B2 or C2. Since B2 and C2 both virtual inherit from A, when
// they are constructed, only 1 copy of the base A class is made and the vtable pointer info
// is "shared" between the 2 base objects (B2 and C2), and the calls are no longer ambiguous
struct Z2 : public B2, public C2
{
    Z2() { tracefunc; }
};


int _tmain(int argc, _TCHAR* argv[])
{
    // gets 2 "copies" of the 'A' base since 'B1' and 'C1' don't virtually inherit from 'A'
    Z1 z1;
    // gets only 1 "copy" of 'A' base since 'B2' and 'C2' virtualy inherit from 'A' and thus "share" the vtable pointer to the 'A' base
    Z2 z2;

    z1.write(); // ambiguous call to write (which one is it .. B1::write() (since B1 inherits from A) or A::write() ?)
    z1.read(); // ambiguous call to read (which one is it .. C1::read() (since C1 inherits from A) or A::read() ?)

    z2.write(); // not ambiguous: z2.write() calls C2::write() since it's "virtually mapped" to/from A::write() 
    z2.read(); // not ambiguous: z2.read() calls B2::read() since it's "virtually mapped" to/from A::read() 
    return 0;
}

虽然它可能很明显&#34;对于我们人类,我们打算在z1变量的情况下调用,因为B1没有write方法,我会&#34;期待&#34 ;编译器选择C1::write方法,但由于对象的内存映射如何工作,它会出现问题,因为A对象中C1的基本副本可能有不同的信息(指针/引用/句柄)而不是A对象中B1库的副本(因为技术上有A个基础的2个副本);因此,对B1::read() { this->write(); }的调用可能会产生意外行为(虽然未定义)。

基类说明符上的virtual关键字明确表示其他几乎从相同基类继承的类,只能获得基类型的1个副本。

请注意,上述代码无法编译,编译错误解释了对z1对象的模糊调用。如果您注释掉z1.write();z1.read();行,则输出(至少对我而言)如下:

A::A
B1::B1
A::A
C1::C1
Z1::Z1
A::A
B2::B2
C2::C2
Z2::Z2
C2::write
B2::read

请注意构建A之前A::A ctor(Z1)的2次调用,而Z2只有1次调用A构造函数。

我建议阅读the following on virtual inheritance,因为它更深入地注意到其他一些需要注意的陷阱(就像虚拟继承的类需要使用初始化列表来进行基类ctor调用一样,或者在进行这种类型的继承时,你应该避免使用C风格的强制转换。)

它还解释了您最初使用构造函数/析构函数命令提到的内容,更具体地说明了在使用多个虚拟继承时如何完成排序。

希望能帮助澄清一些事情。

答案 1 :(得分:1)

编译器的输出是正确的。实际上,这是关于虚拟继承的目标。虚拟继承旨在解决多重继承中的“钻石问题”。例如,B继承自A,C继承自A,D继承自B,C。图如下:

    A
   | |
   B C
   | |
    D

因此,D有两个来自B和C的实例A.如果A有虚函数,则存在问题。

例如:

struct A
{
    virtual void foo(){__builtin_printf("A");}
    virtual void bar(){}
};

struct B : A
{
    virtual void foo(){__builtin_printf("B");}
};

struct C : A
{
    virtual void bar(){}
};

struct D : B, C
{

};

int main()
{
    D d;
    d.foo(); // Error
}

如果我使用我的xlC编译器编译并运行:

xlC -+ a.C

错误信息是这样的:

a.C:25:7: error: member 'foo' found in multiple base classes of different types
    d.foo(); // Error
      ^
a.C:9:18: note: member found by ambiguous name lookup
    virtual void foo(){__builtin_printf("B");}
                 ^
a.C:3:18: note: member found by ambiguous name lookup
    virtual void foo(){__builtin_printf("A");}
                 ^
1 error generated.
Error while processing a.C.

错误信息非常清楚,成员'foo'在不同类型的多个基类中找到。如果我们添加虚拟继承,问题就解决了。因为A的构造权由D处理,所以有一个A的实例。

回到你的代码,继承图是这样的:

A     A
|     |
B1    B2
|     |
C1    C2

没有'钻石问题',这只是单一的继承。因此,构造顺序也是A-> B2-> C2,输出没有区别。

答案 2 :(得分:0)

您无法看到输出中的任何差异,因为以下任何类级别的输出都是相同的:

Hiearchy 1

class A {};

class  B2 : virtual public A {};

class  C2 : virtual public B2 {};

Hiearchy 2

class A {};

class  B2 : public A {};

class  C2 : virtual public B2 {};

Hiearchy 3

class A {};

class  B2 : virtual public A {};

class  C2 : public B2 {};

Hiearchy 3

class A {};

class  B2 : public A {};

class  C2 : public B2 {};

在所有这些情况下,A::A()将首先执行,然后是B2::B2(),然后是C2::C2()

它们之间的区别在于A::A()何时被调用。它是从B2::B2()还是C2::C2()调用的?

我对 Hiearchy 1 的回答并非100%明确。我认为B2::B2()应该从C2::C2调用,因为B2C的虚拟基类。应该从A::A()调用B2:B2(),因为AB2的虚拟基类。但我确切的顺序可能是错的。

层次结构2 中,A::A()将从B2::B2()调用。由于B2virtual的{​​{1}}基类,C2会从B2::B2()调用C2::C2()。由于AB2的正常基类,A::A()会从B2::B2()调用A::A()

层次结构2 中,C2::C2()将从A调用。由于A::A()是虚拟基类,因此从C2::C2()调用B2::B2()。在A::A()的调用完成后调用A::A()

层次结构4 中,B2::B2()将从#include <iostream> class A { public: A(char const *from) { std::cout << "Called from : " << from << std::endl; } }; class B2 : virtual public A { public: B2() : A("B2::B2()") {} }; class C2 : virtual public B2 { public: C2() : A("C2::C2()") {} }; int main() { C2 c; } 调用。我认为这个案子不需要解释。

为澄清我对 Hiearchy 1 的疑虑,我使用了以下程序:

Called from : C2::C2()

我得到了以下输出:

A::A()

这证实了@ T.C在评论中指出的内容,这与我的预期不同。 C2::C2来自B2::B2,而不是来自Array ( [0] => Array ( [NumberRenamed] => 2 [TopicName] => Contacts [UpperLevelNumberRenamed] => 0 ) [1] => Array ( [NumberRenamed] => 3 [TopicName] => Legal services [UpperLevelNumberRenamed] => 1 ) [2] => Array ( [NumberRenamed] => 1 [TopicName] => Services [UpperLevelNumberRenamed] => 0 ) [3] => Array ( [NumberRenamed] => 4 [TopicName] => Accounting services [UpperLevelNumberRenamed] => 1 ) )