使用虚拟继承和委托构造函数在构造函数中崩溃

eXX*_*XX2 8 c++ virtual-inheritance visual-c++ c++11 delegating-constructor

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;
}
Run Code Online (Sandbox Code Playgroud)

使用上面的代码我遇到MSVC 2013编译器崩溃.使用GCC 4.7.2编译时,它运行时没有崩溃.类的层次结构如下所示.

         D
       /  \
     D1    D2
      |     |
       \    B
        \  /
         A
Run Code Online (Sandbox Code Playgroud)

这是MS编译器中的一个错误,或者我在代码中犯了错误?

AnT*_*AnT 6

通过MSVC++ 2013编译器生成的汇编代码的快速检查表明,从委派呼叫B::B(int)B()被不正确地制造.这是编译器中的一个错误.

MSVC++构造函数有一个隐藏的布尔参数,告诉构造函数它是构造一个派生程度最大的对象(true)还是嵌入式基础子对象(false).在这个例子中,只A::A()应该true在这个隐藏参数中接收,而所有低级构造函数调用都应该接收false.但是,当B()调用from时B::B(int),编译器无条件地传递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()`
...
Run Code Online (Sandbox Code Playgroud)

在编译器进行委托构造函数调用时正确生成的代码中,它应该传递从调用者接收的参数值,而不是硬编码1.

A::A()在此示例中进行的立即子构造函数调用的顺序如下:1)公共虚拟基础D,2)基础B,3)基础D1.

根据语言规则,在这种情况下,构造函数B和构造函数D1不应构造其虚拟基础D.Base D已经由最派生的对象在那时构建A.这正是由隐藏的布尔参数控制的内容.但是,当B::B()调用from时B::B(int),编译器会传递一个不正确的参数值(硬编码1),这会导致B::B()错误地认为它构造了一个派生程度最高的对象.这转而B重新构建了公共虚拟基础D.这种重建优先于已经完成的正确施工的结果A::A().后来这会导致崩溃.


小智 0

据我所知,您的代码示例应该可以工作。

不过,顺便说一句,您的构造函数委托可能被认为是不好的做法。您应该有一个完全定义的构造函数,所有较少定义的构造函数都委托给该构造函数,而不是相反。例如:

struct B : public D2
{
    B() : B(0) { }

    B(int val) { }

    void m() const { }
};
Run Code Online (Sandbox Code Playgroud)