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

Posted

技术标签:

【中文标题】使用虚拟继承和委托构造函数在构造函数中崩溃【英文标题】:Crash in constructor with using virtual inheritance and delegating constructors 【发布时间】:2014-09-27 13:22:15 【问题描述】:
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

这是MS编译器的错误还是我在代码中犯了错误?

【问题讨论】:

据我所见,A a; 而不是 D* d = new A; 也表现错误。 可能是这个bug:connect.microsoft.com/VisualStudio/feedbackdetail/view/841488 它也可以在带有 Clang 的 Mac OS 上运行。如果您真的想全面诊断,您可能需要生成汇编代码并深入研究。 很好奇..现在我必须获得 vs 2013 才能尝试 @singh,这只是构造函数的委托。它应该是合法的。 【参考方案1】:

对 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() 已经进行的正确构造的结果。稍后这会导致崩溃。

【讨论】:

【参考方案2】:

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

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

struct B : public D2

    B() : B(0)  

    B(int val)  

    void m() const  
;

【讨论】:

以上是关于使用虚拟继承和委托构造函数在构造函数中崩溃的主要内容,如果未能解决你的问题,请参考以下文章

[C++11 类的改进] --- 继承构造函数和委托构造函数

[C++11 类的改进] --- 继承构造函数和委托构造函数

C++11 继承构造函数和委托构造函数

C++11 继承构造函数和委托构造函数

本周小贴士#74:委托和继承构造函数

使用虚拟继承时调用默认构造函数[重复]