【问题标题】:Finalise Virtual inheritance完成虚拟继承
【发布时间】:2018-12-17 11:47:35
【问题描述】:

在我的代码中,我有一个基本的菱形图案:

     CommonBase
        /  \
       /    \
 DerivedA  DerivedB
       \    /
        \  /
       Joined

它是这样实现的,通用基类有一个默认构造函数和一个带参数的构造函数:

struct CommonBase {
    CommonBase() : CommonBase(0) {}

    CommonBase(int val) : value(val) {}

    const int value;
};

struct DerivedA : public virtual CommonBase {
    void printValue() {
        std::cout << "The value is " << value << "\n";
    }
};

struct DerivedB : public virtual CommonBase {
    void printValueTimes2() {
        std::cout << "value * 2 is " << value * 2 << "\n";
    }
};

struct Joined : public DerivedA,
                public DerivedB {
    Joined(int val) : CommonBase(val) {
        std::cout << "Constructor value is " << val << "\n";
        std::cout << "Actual value is " << value << "\n";
    }
};

Joined 类使用带参数的构造函数初始化虚基,一切都按预期工作。

但是,当我从 Joined 类派生一个类时,会发生一些奇怪的事情 - 除非我在派生类构造函数中显式初始化 CommonBase,否则会调用 CommonBase默认构造函数也是。

使用以下代码演示:

struct JoinedDerivedA : public Joined {
    JoinedDerivedA() : Joined(99) {
        printValue();
    }
};

struct JoinedDerivedB : public Joined {
    JoinedDerivedB() : Joined(99), CommonBase(99) {
        printValue();
    }
};

int main() {
    std::cout << "======= Joined =======\n";
    Joined j(99);
    j.printValue();
    std::cout << "\n=== JoinedDerivedA ===\n";
    JoinedDerivedA a;
    std::cout << "\n=== JoinedDerivedB ===\n";
    JoinedDerivedB b;

    return 0;
}

这段代码的输出是

======= Joined =======
Constructor value is 99
Actual value is 99
The value is 99

=== JoinedDerivedA ===
Constructor value is 99
Actual value is 0 // <-- unexpected behaviour
The value is 0

=== JoinedDerivedB ===
Constructor value is 99
Actual value is 99
The value is 99

为什么会这样?是否可以不必再次显式初始化派生类中的公共基类?

这是ideone上的代码,你可以自己运行:https://ideone.com/Ie94kb

【问题讨论】:

  • 当您拥有虚拟继承时,每个人都只有一个共享的基类子对象,该子对象虚拟地派生自该基类。并且该子对象存在于最派生的类中。它还应该住在哪里?真的没有任何其他地方可以始终以明确的方式放置它。你可能想看看stackoverflow.com/questions/6461784/…
  • @MichaelKenzel 我知道共享基类只有一个实例,但是我希望它存在于Joined 类中,因为该类显式调用基类构造函数,类似于非虚拟继承。
  • 如果有另一个基类在其构造函数中也调用了虚基类的构造函数呢?
  • 这是一个非常好的问题,如果有人能解释为什么它是在 C++ 中以这种方式实现的,而不是仅仅声明最派生类必须初始化虚拟基地。

标签: c++ inheritance diamond-problem


【解决方案1】:

这在 初始化基和成员 [class.base.init] 中指定(草案 n4567 中的 12.6.2)。我们可以阅读§13(强调我的):

(13) 在非委托构造函数中,初始化按以下顺序进行:

(13.1) — 首先,并且只针对最派生类(1.8)的构造函数,虚基类被初始化 它们出现在基类的有向无环图的深度优先从左到右遍历中的顺序, 其中“从左到右”是派生类基说明符列表中基类的出现顺序。

(13.2) — 然后,直接基类按照声明顺序被初始化,因为它们出现在 base-specifier-list 中 (不管 mem-initializers 的顺序如何)。

(13.3) — 然后,非静态数据成员按照它们在类定义中声明的顺序进行初始化 (同样不管 mem-initializers 的顺序如何)。

(13.4) — 最后,构造函数体的复合语句被执行。

[ 注意:声明顺序必须确保基子对象和成员子对象在 初始化的相反顺序。 ——尾注]

这意味着虚拟基类将在Joined 的初始化之前被初始化。所以在DerivedJoinedA中,默认初始化为value为0。然后在初始化Joined时,CommonBase的初始化被忽略,因为它已经被初始化了,value保持它的0值。

这就是为什么必须在最派生类中初始化虚拟基类的原因。

【讨论】:

  • 感谢您的详细解释。参考标准的解释是我所希望的!
猜你喜欢
  • 2012-09-27
  • 2016-10-26
  • 2013-08-24
  • 1970-01-01
  • 2016-03-26
  • 2011-01-08
相关资源
最近更新 更多