【问题标题】:Calling a virtual base class's overloaded constructor调用虚拟基类的重载构造函数
【发布时间】:2011-03-30 12:53:37
【问题描述】:

是否有一种(实用的)方法可以绕过正常(虚拟)构造函数调用顺序?

示例:

class A
{
    const int i;

public:
    A()
      : i(0)
    { cout << "calling A()" << endl; }

    A(int p)
      : i(p)
    { cout << "calling A(int)" << endl; }
};

class B
    : public virtual A
{
public:
    B(int i)
      : A(i)
    { cout << "calling B(int)" << endl; }
};

class C
    : public B
{
public:
    C(int i)
      : A(i), B(i)
    { cout << "calling C(int)" << endl; }
};

class D
    : public C
{
public:
    D(int i)
      : /*A(i), */ C(i)
    { cout << "calling D(int)" << endl; }
};


int main()
{
    D d(42);
    return 0;
}

输出:

调用 A()
调用 B(int)
调用 C(int)
调用 D(int)

我想要的是这样的:

调用 A(int)
调用 B(int)
调用 C(int)
调用 D(int)


如您所见,其中涉及到虚拟继承,导致D的构造函数首先调用A的构造函数,但是由于没有提供参数,所以它调用了A()。 const int i 需要初始化,所以我遇到了问题。

我想做的是隐藏 C 的继承细节,这就是为什么我正在寻找一种方法来避免在 D(以及每个派生的)构造函数的初始化列表中调用 A(i)。 [编辑] 在这种特定情况下,我可以假设只有 C 的非虚拟单继承子类(因为 D 是一个)。 [/编辑]

[编辑]

虚拟基类在初始化任何非虚拟基类之前被初始化,因此只有最派生的类才能初始化虚拟基类。 ——詹姆斯·麦克内利斯

这正是重点,我希望最派生类调用虚拟基类构造函数。 [/edit]

考虑以下情况(上面的代码示例中没有表示):

  A
 / \
B0  B1
 \ /
  C
  |
  D  

我明白为什么 C 必须在实例化 C 时调用 A 的 ctor(歧义),但为什么 D 在实例化 D 时必须调用它?

【问题讨论】:

  • 我不相信您的代码示例与您提供的输出匹配。你确定你用指令“D d;”来实例化d吗? ?
  • sry,我忘记了参数......现在是 D d(42)。谢谢。
  • 好吧,这似乎更公平 :-) 请问您为什么要使用“可怕的钻石”架构?你不能用其他方式重新组织你的代码吗?
  • ^^ 是的,这总是有可能的。当实在没有可行的方法时,我会选择重新设计。
  • @Benoît 因为“钻石”并不“可怕”,所以它是一个非常好的工具。

标签: c++ constructor virtual-inheritance


【解决方案1】:

这就是规则。有用于覆盖虚拟函数的规则和用于构造虚拟基础子对象的规则。尽管两者在概念上非常相似,但它们遵循完全不同的规则,原因是:重写虚函数是显式的。对于默认构造函数,调用构造函数是隐式的。

虚拟基类中的虚拟函数只需要有一个最终覆盖器,即覆盖所有其他覆盖器的覆盖器。 (非虚基类中的虚函数不可能有两个覆盖器,一个不覆盖另一个。)

但是虚拟基类的构造函数总是从最派生的类中调用,并且通常以隐式的形式在 ctor-init-list 中不提及虚拟基类,因为大多数类都被设计为用作虚拟基类类是“纯接口”,没有数据成员,也没有用户初始化。

【讨论】:

    【解决方案2】:

    不幸的是,您总是必须从最派生的类调用虚拟基类构造函数。

    这是因为您说虚拟基在从它派生的所有类之间共享,以用于对象的实例。由于对于给定的对象实例化只能调用一次构造函数,因此您必须在派生最多的类中显式调用构造函数,因为编译器不知道有多少类共享虚拟基(从 The C++ 编程语言第 3 版,第 15.2.4.1 节)。这是因为编译器将从最基类的构造函数开始,并工作到最派生类。根据标准,直接从虚拟基类继承的类不会调用其虚拟基类构造函数,因此必须显式调用。

    【讨论】:

    • > 直接从虚拟基类继承的类,按照标准不会调用其虚拟基类构造函数,因此必须显式调用。对B类来说是正确的,然后C类可以调用它。但由于 C 不是派生最多的类,因此 D 必须调用它。有什么办法让C调用它? D 直接从具有单一非虚拟继承的 C 派生。
    • @DyP:假设我稍后添加一个派生自 C 的类 E,然后使 D 从 C 和 E 乘派生(即使我没有使 C 成为虚拟),那么 C 调用构造函数对于 A?
    • 抱歉,我编辑了这个问题。我可以假设 C 没有多重继承。
    • @DyP:对于您的具体情况,它不会有任何问题,但是该语言是为一般情况编写的,其中包括我作为示例给出的内容。确保在所有可能情况下只调用一次虚拟基类的构造函数的最简单方法是让最派生类负责调用它。
    • 我同意并理解(现在)为什么要这样设计。这就是为什么我要求这个规则的解决方法(两阶段初始化或使用继承或其他东西)
    【解决方案3】:

    我明白为什么 C 必须调用 A 的 ctor (歧义)当你 实例化C,但为什么D必须 在实例化 D 时调用它?

    出于同样的原因,C 必须调用它。这不是模棱两可的问题,而是 A 的构造函数只能被调用一次(因为它是一个虚基)。

    如果您希望 C 能够初始化 A 的构造函数,那么如果 D 类继承 C 和另一个最终继承 A 的类呢?

    【讨论】:

    • > [...] 如果 D 类继承两个 C 的 [...] 会怎样?那我不是实际上是从 C 派生的吗?
    • @DyP:是的,关于两个 C 的公平点。我已经从我的答案中编辑了它。当然,这一点仍然存在。
    • @DyP:没有必要,如果 C 可以支持在一个对象中拥有多个自身的副本,它就不必是虚拟的。诚然,你会想要这样的情况真的很少见,但可以用这种语言来做。
    • “这不是模棱两可的问题 [...]”,是的,我想说如果在实例化 C 时 B 调用它是无稽之谈,因为 C 可以从 B0 和B1(看钻石)。我的具体问题涉及 D,它是 C 的单个非虚拟子类,以及 D 的其他(非虚拟、单继承)子类。我编辑了问题。
    【解决方案4】:

    关于 parashift c++-faq-lite 这个问题is outlined.

    【讨论】:

    • 我相信作者在问为什么你总是必须从最派生的类中调用虚拟基类构造函数,而不是他给出的示例所遇到的特殊问题。这个例子只是为了清楚地说明他的问题。
    • @diverscuba23:我花了一些时间搜索才明白问题所在。现在我明白了,但不明白会导致这种情况的情况。
    猜你喜欢
    • 2021-04-15
    • 2011-09-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-07-16
    • 2013-10-24
    • 2016-04-09
    相关资源
    最近更新 更多