【问题标题】:Understanding virtual base classes and constructor calls了解虚拟基类和构造函数调用
【发布时间】:2011-09-21 15:38:26
【问题描述】:

我对虚拟基类的工作方式有点困惑。特别是,我想知道如何调用基类的构造函数。我写了一个例子来理解它:

#include <cstdio>
#include <string>
using std::string;

struct A{
    string s;
    A() {}
    A(string t): s(t) {}
};

struct B: virtual public A{
    B(): A("B"){}
};

struct C: virtual public A {};

struct D: public B, public C {};

struct E: public C, public B {};

struct F: public B {};

int main(){
    D d;
    printf("\"%s\"\n",d.s.c_str());
    E e;
    printf("\"%s\"\n",e.s.c_str());
    F f;
    printf("\"%s\"\n",f.s.c_str());
    B b;
    printf("\"%s\"\n",b.s.c_str());
}

哪些输出

""
""
""
"B"

我不确定前两种情况会发生什么,但至少对于第三种情况,我期望输出为“B”。所以现在我很困惑。理解 A 的构造函数如何被调用的规则是什么?

【问题讨论】:

  • 结构 F 的最后一种情况在那个问题中没有讨论,尽管也许我应该从答案中理解它。

标签: c++ virtual multiple-inheritance base-class


【解决方案1】:

总是只有一个构造函数调用,而且总是你实例化的实际的、具体的类。 有责任为每个派生类赋予一个构造函数,该构造函数在必要时调用基类的构造函数,就像您在B 的构造函数中所做的那样。

更新:很抱歉错过了您的主要观点!感谢 ildjarn。

但是,您的B实际上继承自A。根据标准(FIDS 中的 10.1.4),“对于每个指定为虚拟的不同基类,最派生的对象应包含该类型的单个基类子对象”。在您的情况下,这意味着在构造基础时,您的类 F 会立即调用 A 的默认构造函数,而不是 B 的。

【讨论】:

  • 这如何回答他的问题? F 的默认构造函数隐式调用B 的默认构造函数,该构造函数将A::s 初始化为"B",但f.s 为空。也就是说,他的问题是为什么this 打印一个空字符串?
  • @ildjarn:你是对的,对不起!这一切都取决于B 几乎继承自A。我会修改的!
  • @Ben:默认构造函数。你可以说F::F() : B(...), A(...) { }。请参阅 DeadMG 的问题 + 源示例中的 cmets。 B 的默认构造函数(或您指定的任何一个)确实会被调用,只是不会从B 的构造函数中调用A 的构造函数,因为正如 DeadMG 所指出的那样,“虚拟基类是构造的按最派生类”,即F.
  • @Kerrek :从技术上讲,虚拟基础子对象在直接基础子对象之前初始化,所以F::F() : A(...), B(...) { } 会更好。我认为 GCC 甚至对F::F() : B(...), A(...) { } 发出警告。
【解决方案2】:

虚拟基类总是由最派生的类构造。

【讨论】:

  • 并且根本没有解释所展示的行为。
  • @Tomalak :它确实解释了所展示的行为,因为 CDEF 负责初始化它们的 A 子对象,而没有人负责;因此,它们的A 子对象始终是默认构造的,尽管后三个继承自B 的红鲱鱼。
  • @ildjarn:哦,我明白了。好的。那么F 能提供那个参数吗?
  • @Tomalak:是的,它直观。 :-] 虚拟继承的语义是不允许多重继承的主要原因之一。 Java 和 C#。
  • @Kerrek : B 的构造函数运行,它根本不初始化它的 A 子对象,因为在这种情况下它不是最派生的类型。见here
猜你喜欢
  • 2021-04-15
  • 2011-03-30
  • 2013-10-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-01-27
相关资源
最近更新 更多