虚拟继承构造函数顺序
virtual inheritance constructor order
我试图更好地理解虚拟继承的概念,以及它的危险。
我在另一篇文章(为什么在虚拟继承中调用默认构造函数?)中看到,它(= virtual inheritance)改变了构造函数调用的顺序(首先调用"祖母",而没有虚拟继承时则不会)。
所以我试了下面看看我得到了这个想法(VS2013):
#define tracefunc printf(__FUNCTION__); printf("rn")
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
这不是我所期望的(我预计两个类的顺序将不同)。
我错过了什么?谁能给我解释一下或者给我介绍一个更好的来源?
谢谢!
在您的示例中,您的输出是预期的。Virtual inheritance
在实例中发挥作用,当你有一个类的多重继承谁的父类也继承自相同的类/类型(即"钻石问题")。在您的示例中,您的类可能被设置为虚拟继承(如果需要在代码的其他地方),但它们不一定基于您的示例"虚拟继承",因为没有派生类(B1/B2/C1/C2
)比直接继承A
做更多。
为了展开,我对您的示例进行了调整,以更多地解释:
#include <cstdio>
#define tracefunc printf(__FUNCTION__); printf("rn")
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;
}
虽然它可能是"明显的"我们人类调用我们打算在z1
变量的情况下,因为B1
没有write
方法,我将"期望"编译器选择C1::write
方法,但由于对象的内存映射如何工作,它提出了一个问题,因为C1
对象中的A
的基副本可能具有与B1
对象中的A
基副本不同的信息(指针/引用/句柄)(因为技术上有2个A
基副本);因此,调用B1::read() { this->write(); }
可能会产生意想不到的行为(尽管不是未定义的)。
基类说明符上的virtual
关键字明确表示,实际上继承同一基类类型的其他类只能获得该基类型的一个副本。
注意,上面的代码应该无法编译,因为编译器错误解释了对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
注意,在Z1
构造之前,A
函数(A::A
)被调用了2次,而Z2
只调用了1次A
构造函数。
我建议阅读下面关于虚拟继承的文章,因为它更深入地介绍了其他一些需要注意的缺陷(比如虚拟继承的类需要使用初始化列表来进行基类的actor调用,或者在进行这种类型的继承时应该避免使用c风格的强制类型转换)。
它还解释了您最初暗示的构造函数/析构函数排序的更多内容,以及更具体地说,在使用多重虚拟继承时如何进行排序。
希望这能帮你弄清楚一点。
编译器输出正确。实际上,这是关于虚拟继承的目标。虚拟继承旨在解决多重继承中的"菱形问题"。例如,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,输出没有差异。
您将无法在输出中看到任何差异,因为在以下任何类层次结构中输出都是相同的:
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()
调用的?
我不是100%清楚的答案等级1。我认为B2::B2()
应该从C2::C2
中调用,因为B2
是C
的虚拟基类。A::A()
应该从B2:B2()
调用,因为A
是B2
的虚拟基类。但我可能弄错了确切的顺序。
在Hierarchy 2中,A::A()
将从B2::B2()
调用。因为B2
是C2
的virtual
基类,所以B2::B2()
从C2::C2()
被调用。由于A
是B2
的普通基类,因此A::A()
从B2::B2()
调用。
在Hierarchy 2中,A::A()
将从C2::C2()
调用。因为A
是一个虚基类,所以A::A()
从C2::C2()
被调用。B2::B2()
在A::A()
调用完成后被调用
在Hierarchy 4中,A::A()
将从B2::B2()
调用。我认为这个情况不需要解释。
为了澄清我对层次结构1的疑问,我使用了以下程序:
#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;
}
得到如下输出:
Called from : C2::C2()
这证实了@T.C在他的评论中所指出的,这与我所期望的不同。A::A()
从C2::C2
调用,而不是从B2::B2
调用
- 类内初始化与构造函数初始化列表的顺序
- 运算符 new 的执行顺序和构造函数的参数
- 如果在 C++ 构造函数中以错误的顺序初始化对象数据,会发生什么类型的错误
- 构造函数中没有参数的对象类成员按什么顺序初始化?
- 用作成员构造函数参数的函数的求值顺序
- 函数中调用的构造函数的顺序
- 构造函数中初始化列表的计算顺序是否固定?
- 构造函数和析构函数的顺序
- 遵循 C++ 中的构造函数执行顺序
- 更改操作的构造函数顺序
- 构造函数的初始值设定项列表中的函数调用是否按顺序排序?
- 如何维护类成员的顺序,并且仍然有一个可工作的构造函数
- 在继承中更改构造函数的顺序
- C++:初始值设定项列表顺序中的构造函数
- 如何在使用 std::make_tuple 时避免构造函数的未定义执行顺序
- 程序不会按照构造函数进行顺序进行,从而导致非初始化的变量
- 构造函数的调用顺序
- 构造函数和析构函数调用的顺序
- 在虚拟继承中构造函数调用的顺序是什么
- STL 中构造函数调用的顺序