如何在编译器中实现C ++虚拟继承?

时间:2011-09-09 11:08:58

标签: c++ inheritance compiler-construction virtual-inheritance

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

在以下代码中:

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时,将使用第一个实例,否则使用第二个实例。

5 个答案:

答案 0 :(得分: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后重置。如果CA的一部分(如果D继承自DCD,则会使用相同的机制来阻止A初始化{{1}}初始化{{1}})。

答案 1 :(得分:8)

它依赖于实现。例如,GCC(请参阅this question)将发出两个构造函数,一个调用A(1),另一个调用{<1}}。

B1()
B2() // no A

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

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

构造C时,将调用基本版本:

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

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

答案 2 :(得分:3)

我建议你阅读一些文章。这两个非常有趣,特别是第一个来自C ++的父亲:

[1] Bjarne Stroustrup. Multiple Inheritance for C++. The C/C++ Users Journal, May 1999.

[2] J. Templ. A Systematic Approach to Multiple Inheritance Implementation. ACM SIGPLAN Notices, Volume 28, No. 4 April 1993.

我在大学进行多重继承的研讨会(作为学生)时,将它们作为主要参考资料。

答案 3 :(得分:3)

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

特别是5.1.4 Other Special Functions and Entities列出了不同目的的不同特殊成员函数:

<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 Definitions部分很有用(但不完整):

  

T类

的基础对象析构函数      

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

     

T类

的完整对象析构函数      

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

     

删除T类

的析构函数      

除了完整对象析构函数所需的动作之外,还调用适当的释放函数的函数(即,。   对于T。

,运算符删除)

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

答案 4 :(得分:0)

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

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

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

如果父类定义了虚方法,并且子类覆盖它,则存储两种方法。每个类都有一个名为“虚方法表”的东西,其中有一个指向每个方法的指针表。

不要担心性能,编译器不会重复方法的代码。