TCS*_*TCS 7 c++ inheritance virtual-inheritance
我试图更好地理解虚拟继承的概念,它的危险是什么.
我在另一篇文章(为什么在虚拟继承中调用默认构造函数?)中读到它(=虚拟继承)改变了构造函数调用的顺序("祖母"首先被调用,而没有虚拟继承它没有).
所以我尝试了以下内容,看看我有了这个想法(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();
}
Run Code Online (Sandbox Code Playgroud)
输出是:
A::A
B1::B1
C1::C1
A::A
B2::B2
C2::C2
Run Code Online (Sandbox Code Playgroud)
这不是我的预期(我预计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;
}
Run Code Online (Sandbox Code Playgroud)
虽然对于我们人类来说,我们打算在变量的情况下进行调用可能是“显而易见的” ,z1但由于B1没有write方法,我“期望”编译器选择该C1::write方法,但由于内存映射的方式对象工作时,它提出了一个问题,因为对象A中的基副本可能具有与对象中基副本C1不同的信息(指针/引用/句柄)(因为技术上有 2 个基副本);因此,调用可能会产生意外的行为(尽管不是未定义的)。AB1AB1::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
Run Code Online (Sandbox Code Playgroud)
请注意,在构造之前有 2 次对Actor ( A::A)的调用,而只有 1 次对构造函数的调用。Z1Z2A
我建议阅读以下有关虚拟继承的内容,因为它更深入地介绍了一些需要注意的其他陷阱(例如虚拟继承的类需要使用初始化列表来进行基类 ctor 调用,或者您应该避免在进行此类继承时使用 C 风格的强制转换)。
它还进一步解释了您最初提到的构造函数/析构函数排序的内容,更具体地说,在使用多个虚拟继承时如何完成排序。
希望这可以帮助澄清一些事情。