11

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

例子:

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 是一个)。[/编辑]

[编辑]

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

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

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

  A
 / \
B0  B1
 \ /
  C
  |
  D  

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

4

4 回答 4

7

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

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

于 2010-08-16T17:12:28.673 回答
2

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

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

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

于 2010-08-16T17:11:33.413 回答
0

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

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

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

于 2017-05-13T17:34:50.073 回答
-1

在 parashift c++-faq-lite上概述了这个问题。

于 2010-08-16T17:05:00.753 回答