使用虚拟继承时初始化基类

使用虚拟继承时初始化基类

本文介绍了使用虚拟继承时初始化基类的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I'm getting unexpected errors when compiling the following code using Xcode 5.1 on OS X.Apple LLVM version 5.1 (clang-503.0.40) (based on 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)的构造函数调用.

My understanding is that the constructor for the virtual base class (GrandParent) is not called by the class that inherits from it (Parent1 or Parent2). Instead, the constructor is called by the constructor of the concrete class (Child).

这是正确的吗?

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

If I supply a default constructor for GrandParent it compiles OK. However, if I construct a child object:

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 是正确的.

So the age of GrandParent when using Parent1 is incorrect but for Parent2 it is correct.

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

Have I misunderstood something? Or could the error be a compiler bug?

更新

如果我将 Parent1 的构造函数更新为:

If I update the ctor of Parent1 (and do the same for Parent2)to:

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

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

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

it now compiles. Inspecting the values reveals:

(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 编译,检查结果正确.

This is clearly not right. Furthermore, the modified code compiles on Windows using VS 2013 Express and the values on inspection are correct.

推荐答案

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

All defined ctors, defaulted or not, must be valid.

虽然虚拟基类的初始化在运行时被跳过,但派生最多的构造函数除外,但它必须仍然有效.

While the initialization of virtual bases is skipped at runtime in all but the most-derived ctor, it must still be valid.

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

Quote from C++14 final draft (n3936):

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),然后

  • 如果实体是具有大括号或相等初始化器的非静态数据成员,并且
    • 构造函数的类是联合 (9.5),并且该联合的其他变体成员没有由 mem-initializer-id
    • 指定
    • 构造函数的类不是联合,并且,如果实体是匿名联合的成员,则该联合的其他成员不会被 mem-initializer-id 指定,实体按照 8.5 中的规定进行初始化;
    • if the entity is a non-static data member that has a brace-or-equal-initializer and either
      • the constructor’s class is a union (9.5), and no other variant member of that union is designated by a mem-initializer-id or
      • the constructor’s class is not a union, and, if the entity is a member of an anonymous union, no other member of that union is designated by a mem-initializer-id, the entity is initialized as specified in 8.5;

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

      [ Note: An abstract class (10.4) is never a most derived class, thus its constructors never initialize virtual base classes, therefore the corresponding mem-initializers may be omitted. —end note ]

      我特别推荐您注意最后一个注释,您可能将其用作理由.
      问题是,注释是非规范性的,并且此注释与其前面的规范性文本完全矛盾.

      I especially commend the last note to your attention, which you might have used as justification.
      Trouble is, notes are non-normative, and this note is flatly contradicted by the normative text preceding it.

      似乎 clang++-3.5.0 把注释当作福音,而 g++-4.9.0 没有:
      http://coliru.stacked-crooked.com/a/ded8d46cc29ac79f

      Seems clang++-3.5.0 took the note as gospel, while g++-4.9.0 did not:
      http://coliru.stacked-crooked.com/a/ded8d46cc29ac79f

      这篇关于使用虚拟继承时初始化基类的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

08-14 08:42