在使用 C++ 进行虚拟继承期间调用构造函数

Posted

技术标签:

【中文标题】在使用 C++ 进行虚拟继承期间调用构造函数【英文标题】:Invoking constructors during virtual inheritance with C++ 【发布时间】:2016-07-19 19:23:04 【问题描述】:

这是我在阅读this section on learncpp.com 时遇到的一个问题。我使用了这里列出的代码,然后稍作改动进行测试。

背景

虚拟继承创建对基类的公共引用,这有两个作用。

首先,它消除了歧义,因为只有一次创建基成员的副本(例如,将 print() 函数添加到 PoweredDevice 并在 main() 中调用它会导致编译器错误)。

其次,最派生类负责调用基构造函数。如果其中一个中间类尝试调用初始化列表中的基本构造函数,则调用应为ignored。

问题

当我编译并运行代码时,它返回:

PoweredDevice: 3
PoweredDevice: 3
Scanner: 1
PoweredDevice: 3
Printer: 2

它应该返回:

PoweredDevice: 3
Scanner: 1
Printer: 2

当我使用 GDB (7.11.1) 跟踪执行时,它表明中间函数也在通过初始化列表调用 PoweredDevice——但这些应该被忽略。 PoweredDevice 的这种多次初始化不会导致任何成员的歧义,但确实给我带来了麻烦,因为代码执行了多次,而它应该只发生一次。对于更复杂的问题,我不习惯使用虚拟继承。

为什么这些中间类仍在初始化基类?这是我的编译器(gcc 5.4.0)的怪癖还是我误解了虚拟继承的工作原理?

编辑:代码

#include <iostream>
using namespace std;

class PoweredDevice

public:
    int m_nPower;
public:
    PoweredDevice(int nPower)
        :m_nPower nPower
    
        cout << "PoweredDevice: "<<nPower<<endl;
    
    void print()  cout<<"Print m_nPower: "<<m_nPower<<endl; 
;

class Scanner : public virtual PoweredDevice

public:
    Scanner(int nScanner, int nPower)
        : PoweredDevice(nPower)
    
        cout<<"Scanner: "<<nScanner<<endl;
    
;

class Printer : public virtual PoweredDevice

public:
    Printer(int nPrinter, int nPower)
        : PoweredDevice(nPower)
    
        cout<<"Printer: "<<nPrinter<<endl;
    
;

class Copier : public Scanner, public Printer

public:
    Copier(int nScanner, int nPrinter, int nPower)
        :Scanner nScanner, nPower, Printer nPrinter, nPower, PoweredDevice nPower
     
;

int main()

    Copier cCopier 1,2,3;
    cCopier.print();
    cout<<cCopier.m_nPower<<'\n';
    return 0;

【问题讨论】:

你做了哪些“小改动”?无论如何你应该在这里发布代码 欢迎来到 Stack Overflow!请将您的问题editminimal reproducible example 或SSCCE (Short, Self Contained, Correct Example) 在编辑中添加了代码。我检查的变体包括基类中的成员值和函数,包括不同的访问说明符。我还尝试更改虚拟继承访问级别。 该错误似乎取决于虚拟基础构造函数与调用它的具有不同数量/类型参数的中间构造函数。现在这真的是在挖掘我的记忆深处。我记得提交了一个错误,即在使用大括号和不同的 ctor 签名时受保护的访问无法正常工作。我以为他们解决了这个问题,但我想还不够……:C 我打开了一个错误:gcc.gnu.org/bugzilla/show_bug.cgi?id=80849。还有这个旧的预先存在的错误,可能相关也可能不相关:gcc.gnu.org/bugzilla/show_bug.cgi?id=55922 【参考方案1】:

这似乎是一个 GCC 错误,当统一初始化与虚拟继承一起使用时触发。


如果我们改变:

Copier(int nScanner, int nPrinter, int nPower)
    :Scanner nScanner, nPower, Printer nPrinter, nPower, PoweredDevice nPower
 

到:

Copier(int nScanner, int nPrinter, int nPower)
    :Scanner (nScanner, nPower), Printer (nPrinter, nPower), PoweredDevice (nPower)
 

错误消失了,它的行为符合预期:

PoweredDevice: 3
Scanner: 1
Printer: 2
Print m_nPower: 3
3

Clang 和 Visual Studio 都能够正确编译原始代码,并给出预期的输出。

【讨论】:

@RyanB 不客气。如果您想检查某些东西是否是特定编译器中的错误或语言问题,您通常可以使用未安装的编译器的在线环境。我喜欢Rextester,它的下拉菜单中有 Clang、GCC 和 VC++。但是,每一个都在一个单独的页面上,因此您应该在切换到不同的页面之前复制您的代码。 有没有人为此提交过错误?否则我们将不得不继续忍受它。 我打开了一个错误:gcc.gnu.org/bugzilla/show_bug.cgi?id=80849。还有这个旧的预先存在的错误,可能相关也可能不相关:gcc.gnu.org/bugzilla/show_bug.cgi?id=55922

以上是关于在使用 C++ 进行虚拟继承期间调用构造函数的主要内容,如果未能解决你的问题,请参考以下文章

C++ 未定义的抽象类构造函数

C++构造和析构

C++ 类的继承三(继承中的构造与析构)

继承 - 如何在 C++ 的继承类中只调用一个构造函数?

C++调用父类的构造函数规则

在继承 C++ 中调用 main 中的参数化构造函数