【问题标题】:Initialising base classes when using virtual inheritance使用虚拟继承时初始化基类
【发布时间】:2014-11-23 18:53:18
【问题描述】:

在 OS X 上使用 Xcode 5.1 编译以下代码时出现意外错误。 Apple LLVM 版本 5.1 (clang-503.0.40)(基于 LLVM 3.4svn)

class GrandParent
{
public:
    GrandParent(int age) : m_age(age)
    {
    }

    virtual ~GrandParent() {}

private:
    GrandParent();
    GrandParent(const GrandParent&);
    const GrandParent& operator=(const GrandParent&);

    int m_age;
};

class Parent1 : public virtual GrandParent
{
public:
    Parent1(int age) : m_age(age)
    {
    }

    virtual ~Parent1() {}

private:
    Parent1();
    Parent1(const Parent1&);
    const Parent1& operator=(const Parent1&);

    int m_age;
};

class Parent2 : public virtual GrandParent
{
public:
    Parent2(int age) : m_age(age)
    {
    }

    virtual ~Parent2() {}

private:
    Parent2();
    Parent2(const Parent2&);
    const Parent2& operator=(const Parent2&);

    int m_age;
};

class Child : public Parent1, public Parent2
{
public:
    Child(int grandParentAge, int parent1Age, int parent2Age, int childAge) :
        GrandParent(grandParentAge),
        Parent1(parent1Age),
        Parent2(parent2Age),
        m_age(childAge)
    {
    }

    virtual ~Child() {}

private:
    Child();
    Child(const Child&);
    const Child& operator=(const Child&);

    int m_age;
};

报告的错误是:

error: inherited virtual base class 'GrandParent' has private default constructor
    Parent1(int age) : m_age(age)
    ^
note: declared private here
    GrandParent();
    ^
error: inherited virtual base class 'GrandParent' has private default constructor
    Parent2(int age) : m_age(age)
    ^
note: declared private here
    GrandParent();

我的理解是,虚拟基类(GrandParent)的构造函数不会被继承自它的类(Parent1 或 Parent2)调用。相反,构造函数由具体类 (Child) 的构造函数调用。

这是正确的吗?

如果我为 GrandParent 提供默认构造函数,它可以编译。但是,如果我构造一个子对象:

Child child(80, 50, 49, 20);

检查一下,我可以看到:

Child) child = {
  Parent1 = {
    GrandParent = (m_age = 49)
    m_age = 50
  }
  Parent2 = {
    GrandParent = (m_age = 80)
    m_age = 49
  }
  m_age = 20

所以使用 Parent1 时 GrandParent 的年龄是不正确的,但对于 Parent2 是正确的。

我是不是误会了什么?或者错误可能是编译器错误?

更新

如果我将 Parent1 的 ctor 更新为:

Parent1(int age) : GrandParent(100), m_age(age) { }

现在可以编译了。检查值显示:

(Child) child = {
  Parent1 = {
    GrandParent = (m_age = 49)
    m_age = 50
  }
  Parent2 = {
    GrandParent = (m_age = 80)
    m_age = 49
  }
  m_age = 20

这显然是不对的。此外,修改后的代码在 Windows 上使用 VS 2013 Express 编译,检查值正确。

【问题讨论】:

  • 不建议在virtualGrandParent 这样的基类中包含数据成员
  • 你的错误不在Child;相反,它在Parent1Parent2 的定义中。
  • @PaulEvans:对<iostream>-designers 这么说。
  • @KerrekSB:需要详细说明吗?
  • @ksl:好吧,你的班级 Parent1 定义不明确。这与所有其他代码的存在无关。您可以删除一半的代码,但仍然有同样的问题。

标签: c++ constructor xcode5 virtual-inheritance ctor-initializer


【解决方案1】:

所有定义的ctors,无论是否默认,都必须是有效的。

虽然虚基的初始化在运行时被跳过除了最派生的ctor之外,它必须仍然有效。

引自 C++14 最终草案 (n3936):

12.6.2 初始化基和成员[class.base.init]

7 mem-initializer 中的 expression-listbraced-init-list 用于初始化指定的子对象(或者,在委托构造函数的情况下,完整的类对象)根据 8.5 的初始化规则进行直接初始化。
[省略示例]
每个 mem-initializer 执行的初始化构成一个完整表达式。 mem-initializer 中的任何表达式都被评估为执行初始化的完整表达式的一部分。 mem-initializer,其中 mem-initializer-id 表示虚拟基类,在执行不是最派生类的任何类的构造函数期间将被忽略.
8 在非委托构造函数中,如果给定的潜在构造子对象不是由 meminitializer-id 指定的(包括没有 mem-initializer-list 的情况,因为构造函数没有ctor-initializer),那么

  • 如果实体是具有 brace-or-equal-initializer 的非静态数据成员,并且
    • 构造函数的类是一个联合 (9.5),并且该联合的其他变体成员没有由 mem-initializer-id
    • 指定
    • 构造函数的类不是联合体,如果实体是匿名联合体的成员,则该联合体的其他成员不会由 mem-initializer-id 指定, 实体按照 8.5 中的规定进行初始化;
  • 否则,如果实体是匿名联合或变体成员 (9.5),则不执行初始化;
  • 否则,实体会被默认初始化 (8.5)

[ 注意:抽象类(10.4)永远不是最派生类,因此它的构造函数从不初始化虚拟基类,因此可以省略相应的 mem-initializers。 ——尾注]

我特别推荐您注意最后一个注释,您可能已将其用作理由。
麻烦的是,注释是非规范性的,而且这个注释与它之前的规范性文本完全矛盾。

似乎 clang++-3.5.0 将注释视为福音,而 g++-4.9.0 则没有:
http://coliru.stacked-crooked.com/a/ded8d46cc29ac79f

【讨论】:

    【解决方案2】:

    当程序执行该行时

     Parent1(int age) : m_age(age) 
    

    它尝试创建一个 Parent1 对象,它是 GrandParent 类型的派生类。而且从Parent1类的构造函数定义中可以看出,它什么也不做,只是通过调用它的默认构造函数来尝试创建一个GrandParent对象。

    Parent1(int age) : m_age(age)   // Here the default constructor of GrandParent is called implicitly.
    {
    }
    

    但是,由于您已将 GranParent 类的默认构造函数定义为私有,因此编译器会给出该错误。

    【讨论】:

    • 这是正确答案。 GrandParent 中的默认构造函数是私有的这一事实是代码无法编译的唯一原因。也就是说,Parent1 和 Parent2 类无法编译,因为 GrandParent 的默认构造函数是私有的!但是您不需要此默认构造函数的定义,因为您在 Child 中调用 GrandParent(int) ctor。 (+1)
    猜你喜欢
    • 2019-02-06
    • 2012-08-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-11-09
    • 2020-06-05
    • 1970-01-01
    相关资源
    最近更新 更多