在抽象类构造函数中,为什么我需要调用永远不会调用的虚拟基础的构造函数?

时间:2014-07-02 08:34:23

标签: c++ class virtual multiple-inheritance language-lawyer

我面对众所周知的“可怕”钻石情况:

  A
 / \
B1 B2
 \ /
  C
  |
  D

A有构造函数A::A(int i)。我还想禁止A的默认实例化,因此我将A的默认构造函数声明为private

B1B2实际上来自A,并且有一些构造函数和protected默认构造函数。

[编辑] B1B2的构造函数不会调用A的默认构造函数。 [重新编辑] B1B2的默认构造函数也不会调用A的默认构造函数。 [重新编辑] [编辑]

C是一个抽象类,有一些构造函数不会调用任何AB1B2构造函数。

在课程D中,我调用构造函数A::A(i)C的一些构造函数。

正如预期的那样,在创建D时,它首先创建一个A来解决可怕的钻石问题,然后创建B1B2和{{1 }}。因此,CAB1中没有调用B2的默认构造函数,因为如果存在,则会创建C的许多实例。

编译器拒绝代码,因为A的默认构造函数是A。如果我将其设置为private则会编译。

我不明白的是,当我运行代码时,永远不会调用protected的默认构造函数(因为它应该是)。那么为什么编译器不允许我将其设置为A

[编辑] 好吧,我会写一个例子......但它很痛; - )

private

编译器说在class A{ public: A(int i):i_(i){}; virtual ~A(){}; protected: int i_; private: A():i_(0){};/*if private => compilation error, if protected => ok*/ }; class B1: public virtual A{ public: B1(int i):A(i){}; virtual ~B1(){}; protected: B1():A(0){}; }; class B2: public virtual A{ public: B2(int i):A(i){}; virtual ~B2(){}; protected: B2():A(0){}; }; class C: public B1, public B2{ public: C(int j):j_(j){}; virtual ~C()=0; protected: int j_; }; C::~C(){}; class D: public C{ public: D(int i,int j):A(i),C(j){}; ~D(){}; }; int main(){ D d(1,2); } 的构造函数中,C是私有的。我同意这一点,但由于A::A()是一个抽象类,因此无法将其实例化为完整对象(但可以通过实例化C将其实例化为基类子对象)。 [编辑]

我在某人的推荐下添加了“language-lawer”标签。

1 个答案:

答案 0 :(得分:2)

C ++没有只能从派生类调用的成员函数的访问控制说明符,但抽象类的构造函数只能通过定义抽象类从派生类中调用。 / p>

编译器无法事先知道实际确定了哪些类(这是一个运行时属性),并且它无法知道在链接时可能调用哪些构造函数。

标准文本(强调我的):

  

表示虚拟基类的所有子对象都由初始化   派生类最多的构造函数(1.8 [intro.object])。如果   最派生类的构造函数未指定   虚拟基类V的mem-initializer,然后是V的默认值   调用构造函数来初始化虚拟基类子对象。   如果V没有可访问的默认构造函数,则   初始化是不正确的。用于命名虚拟基础的mem-initializer   在任何构造函数的执行期间,将被忽略   不是派生类最多的类。

1)抽象类也不例外,并且只能解释为所有构造函数都应该(有时是假的)尝试调用虚拟基础构造函数。

2)它表示在运行时时会忽略此类尝试。

一些委员会成员在DR 257中提出了不同意见:

  
      
  1. 抽象基础构造函数和虚拟基础初始化
  2.         

    栏目:12.6.2 [class.base.init]状态:CD2发布者:Mike   米勒日期:2000年11月1日[2009年10月会议投票通过WP。]

         

    抽象基类的构造函数必须提供   每个虚拟基类的mem-initializer直接来自   或间接衍生?自从虚拟基础初始化   类由最派生的类执行,并且由抽象执行   基类永远不会是最派生的类,似乎有   没有理由要求抽象基类的构造函数   初始化虚拟基类。

         

    标准中是否确实存在这样的问题尚不清楚   要求与否。相关文本见12.6.2   [class.base.init]第6段:

(......上面引用)

  

这一段只要求派生程度最高的类的构造函数   有一个虚拟基类的mem-initializer。沉默应该是   被解释为对不属于的类别的构造者的许可   最常见的是省略了这样的mem-initializers?

没有"沉默"。一般规则适用于抽象类没有特定的规则

  

克里斯托弗莱斯特,comp.std.c ++,2004年3月19日:如果你们中的任何人   阅读此帖子恰好是上述工作组的成员,   我想鼓励您查看包含的建议   其中,在我看来,提交的最终期限是   (a)正确(标准DOES的沉默授权)   省略)和(b)描述了大多数用户会直观地期望的内容   并且也希望使用C ++语言。

     

建议是使抽象的构造函数更清楚   不应要求基类为任何基类提供初始化   它们包含的虚拟基类(因为只有派生最多的类具有)   初始化虚拟基类的工作和抽象基础   class不可能是派生最多的类。)

这个建议不能让人更清楚"现在不存在的东西。

一些委员会成员对现实的渴望,这是非常错误的。

(剪辑示例和类似于OP代码的讨论)

  

提议的决议(2009年7月):

     

将指示的文本(从第11段移到)添加到12.6.2的末尾   [class.base.init]第7段:

     

...每个基地和成员的初始化构成一个   充分表达。 mem-initializer中的任何表达式都被评估为   执行初始化的全表达式的一部分。一个   mem-initializer,其中mem-initializer-id命名虚拟基础   在执行任何类的构造函数时,将忽略class   不是派生最多的阶级。

     

更改12.6.2 [class.base.init]   第8段如下:

     

如果给定的非静态数据成员或基类未由a命名   mem-initializer-id(包括没有的情况)   mem-initializer-list因为构造函数没有ctor-initializer)   实体不是抽象类的虚拟基类(10.4   [class.abstract]),然后

     

如果实体是具有的非静态数据成员   brace-or-equal-initializer,实体按照中的规定进行初始化   8.5 [dcl.init];

     

否则,如果实体是变体成员(9.5 [class.union]),则不   执行初始化;

     

否则,实体默认初始化(8.5 [dcl.init])。

     

[注意:抽象类(10.4 [class.abstract])绝不是最重要的   派生类,因此它的构造函数永远不会初始化虚拟基础   因此,可以省略相应的mem初始化器。   -end note]调用类X的构造函数后   完成...

     

更改12.6.2 [class.base.init]第10段如下:

     

初始化应按以下顺序进行:

     

首先,仅适用于派生程度最高的类的构造函数   如下所述(1.8 [intro.object]),虚拟基类应为   按照它们在深度优先上出现的顺序进行初始化   从左到右遍历基类的有向无环图,   其中“从左到右”是基类出现的顺序   派生类base-specifier-list中的名称。

     

然后,直接基类应在声明中初始化   它们出现在base-specifier-list中的顺序(无论是什么   mem-initializers的顺序。)

     

然后,应按顺序初始化非静态数据成员   它们在类定义中被声明(再次无论如何   mem-initializers的顺序。)

     

最后,执行构造函数体的复合语句。

     

[注意:声明命令的目的是确保基础和   成员子对象以相反的顺序销毁   初始化。 - 后注]

     

删除12.6.2 [class.base.init]第11段中的所有规范性文字,   保持这个例子:

     

表示虚拟基类的所有子对象都由初始化   派生类最多的构造函数(1.8 [intro.object])。如果   最派生类的构造函数未指定   虚拟基类V的mem-initializer,然后是V的默认值   调用构造函数来初始化虚拟基类子对象。   如果V没有可访问的默认构造函数,则   初始化是不正确的。用于命名虚拟基础的mem-initializer   在执行任何构造函数时,应忽略class   不是派生类最多的类。 [实施例:...

DR被标记为" CD2":委员会同意这是一个问题,并且语言定义已更改以解决此问题。