2

在 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();

我的理解是,从它继承的类(Parent1 或 Parent2)不会调用虚拟基类(GrandParent)的构造函数。相反,构造函数由具体类 (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

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

4

2 回答 2

2

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

虽然在运行时除了最派生的 ctor 之外的所有 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指定的(包括由于构造函数没有ctor-initializer而没有mem-initializer-list的情况),则

  • 如果实体是具有大括号或相等初始化器的非静态数据成员,并且
    • 构造函数的类是一个联合(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

于 2014-09-29T12:23:51.240 回答
1

当程序执行该行时

 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 类的默认构造函数定义为私有,因此编译器会给出该错误。

于 2014-09-29T11:44:00.823 回答