27

编译器如何实现虚拟继承?

在以下代码中:

class A {
  public:
    A(int) {}
};

class B : public virtual A {
  public:
    B() : A(1) {}
};

class C : public B {
  public:
    C() : A(3), B() {}
};

编译器是否生成两个B::ctor函数实例,一个没有A(1)调用,一个有调用?因此,当B::constructor从派生类的构造函数调用时,使用第一个实例,否则使用第二个。

4

5 回答 5

9

它依赖于实现。例如, GCC(参见这个问题)将发出两个构造函数,一个带有对 的调用A(1),另一个没有。

B1()
B2() // no A

当 B 被构造时,“完整”版本被称为:

B1():
    A(1)
    B() body

构造 C 时,会改为调用基本版本:

C():
    A(3)
    B2()
       B() body
    C() body

事实上,即使没有虚拟继承,也会发出两个构造函数,并且它们是相同的。

于 2011-09-09T11:33:55.917 回答
8

编译器不会创建 B 的另一个构造函数,但它会忽略A(1). 由于A是虚拟继承的,因此首先使用其默认构造函数构造它。并且由于它在B()被调用时已经构建,因此该A(1)部分被忽略。

编辑 - 我错过了构造函数初始化列表A(3)中的部分。C当使用虚拟继承时,只有最派生的类才初始化虚拟基类。所以A将使用A(3)而不是其默认构造函数来构造。其余的仍然存在 -A中间类(此处B)的任何初始化都将被忽略。

编辑2,试图回答有关上述实施的实际问题:

在 Visual Studio(至少 2010 年)中,使用一个标志而不是有两个B(). 由于B实际上继承自A,因此在调用A的构造函数之前,会检查该标志。如果未设置标志,A()则跳过调用。然后,在从 派生的每个类中B,标志在初始化后被重置A。如果它是 some 的一部分,则使用相同的机制来防止C初始化(如果继承自, 将初始化)。ADDCDA

于 2011-09-09T11:15:31.713 回答
4

我建议你阅读一些论文。这两个真的很有趣,尤其是第一个,因为它来自 C++ 的父亲:

[1] Bjarne Stroustrup。C++的多重继承。C/C++ 用户杂志,1999 年 5 月。

[2] J.坦普尔。多继承实现的系统方法。ACM SIGPLAN 通知,第 28 卷,1993 年 4 月 4 日。

在我的大学里(作为学生)举办关于多重继承的研讨会时,我将它们用作主要参考资料。

于 2011-09-09T11:49:01.973 回答
3

Itanium C++ ABI是解决所有问题的有用资源,例如“C++ 编译器如何实现它”。

特别是5.1.4 其他特殊功能和实体列出了用于不同目的的不同特殊成员功能:

<ctor-dtor-name> ::= C1   # complete object constructor
             ::= C2   # base object constructor
             ::= C3   # complete object allocating constructor
             ::= D0   # deleting destructor
             ::= D1   # complete object destructor
             ::= D2   # base object destructor

1.1 定义部分很有用(但不完整):

类 T 的基对象析构函数

为 T 的非静态数据成员和 T 的非虚拟直接基类运行析构函数的函数。

类 T 的完整对象析构函数

一个函数,除了基对象析构函数所需的操作外,还运行 T 的虚拟基类的析构函数。

删除类 T 的析构函数

一个函数,除了完整的对象析构函数所需的操作之外,还为 T 调用适当的释放函数(即,运算符删除)。

从这些定义中,完整对象构造器和基础对象构造器的目的是显而易见的。

于 2012-08-05T06:23:30.077 回答
0

如前所述,它取决于编译器的实现。

但是,通常程序员每次添加一个新方法时,都会存储在代码中,即使有另一个具有相同 id 的方法。其他地方(“覆盖”或“重载”)。

每个方法的代码只存储一次,因此如果一个类从父类继承并使用相同的方法,在内部,它使用指向代码的指针,它不会复制代码。

如果父类定义了一个虚方法,而子类重写了它,那么这两个方法都会被存储。每个类都有一个叫做“虚拟方法表”的东西,其中有一个指向每个方法的指针表。

不用担心性能,编译器不会为方法复制代码。

于 2011-09-09T15:38:53.477 回答