我面对众所周知的“可怕”钻石情况:
A
/ \
B1 B2
\ /
C
|
D
类A
有构造函数A::A(int i)
。我还想禁止A
的默认实例化,因此我将A
的默认构造函数声明为private
。
类B1
和B2
实际上来自A
,并且有一些构造函数和protected
默认构造函数。
[编辑]
B1
和B2
的构造函数不会调用A
的默认构造函数。
[重新编辑]
B1
和B2
的默认构造函数也不会调用A
的默认构造函数。
[重新编辑]
[编辑]
类C
是一个抽象类,有一些构造函数不会调用任何A
,B1
或B2
构造函数。
在课程D
中,我调用构造函数A::A(i)
和C
的一些构造函数。
正如预期的那样,在创建D
时,它首先创建一个A
来解决可怕的钻石问题,然后创建B1
,B2
和{{1 }}。因此,C
,A
和B1
中没有调用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”标签。
答案 0 :(得分:2)
C ++没有只能从派生类调用的成员函数的访问控制说明符,但抽象类的构造函数只能通过定义抽象类从派生类中调用。 / p>
编译器无法事先知道实际确定了哪些类(这是一个运行时属性),并且它无法知道在链接时可能调用哪些构造函数。
标准文本(强调我的):
表示虚拟基类的所有子对象都由初始化 派生类最多的构造函数(1.8 [intro.object])。如果 最派生类的构造函数未指定 虚拟基类V的mem-initializer,然后是V的默认值 调用构造函数来初始化虚拟基类子对象。 如果V没有可访问的默认构造函数,则 初始化是不正确的。用于命名虚拟基础的mem-initializer 在任何构造函数的执行期间,将被忽略 不是派生类最多的类。
1)抽象类也不例外,并且只能解释为所有构造函数都应该(有时是假的)尝试调用虚拟基础构造函数。
2)它表示在运行时时会忽略此类尝试。
一些委员会成员在DR 257中提出了不同意见:
- 抽象基础构造函数和虚拟基础初始化
醇>栏目: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":委员会同意这是一个问题,并且语言定义已更改以解决此问题。