struct D { virtual void m() const = 0; }; struct D1 : public virtual D { }; struct D2 : public virtual D { }; struct B : public D2 { B() { } B(int val) : B() { } void m() const { } }; struct A : public B,public D1 { A() : B(0) { } }; int main() { A a; return 0; }
使用上面的代码,MSVC 2013编译器崩溃了.使用GCC 4.7.2编译时,它运行时没有崩溃.类的层次结构如下所示.
D / \ D1 D2 | | \ B \ / A
解决方法
快速检查MSVC 2013编译器生成的汇编代码,可以看出从B :: B(int)到B()的委托调用是错误的.这是编译器中的一个错误.
MSVC构造函数有一个隐藏的布尔参数,告诉构造函数它是构造一个最派生的对象(true)还是一个嵌入式的基础子对象(false).在此示例中,只有A :: A()在此隐藏参数中应该接收true,而所有较低级别的构造函数调用都应该接收false.但是,当从B :: B(int)调用B()时,编译器无条件地将1(true)作为该隐藏参数传递.这是不正确的.
; Code for `B::B(int)` ... 00F05223 push 1 ; <- this is the problem 00F05225 mov ecx,dword ptr [this] 00F05228 call B::B (0F010F0h) ; <- call to `B::B()` ...
在编译器进行委托构造函数调用时正确生成的代码中,它应该传递从调用者接收的参数值,而不是硬编码的1.
在此示例中,从A :: A()进行的立即子构造函数调用的顺序如下:1)公共虚拟基数D,2)基数B,3)基数D1.
根据语言的规则,在这种情况下,B的构造函数和D1的构造函数不应该构造它们的虚拟基础D.基础D已经由最派生的对象A在该点构造.这正是由隐藏的布尔参数.但是,当从B :: B(int)调用B :: B()时,编译器传递一个不正确的参数值(硬编码为1),这导致B :: B()错误地认为它构造了一个派生最多的宾语.这使得B重新构建公共虚拟基础D.这种重构将覆盖A :: A()已经进行的正确构造的结果.后来这会导致崩溃.