在抽象类构造函数中,为什么我需要调用一个永远不会调用的虚拟基址的构造函数? [英] In an abstract class constructor, why I do need to call a constructor of a virtual base that will never to called?

查看:143
本文介绍了在抽象类构造函数中,为什么我需要调用一个永远不会调用的虚拟基址的构造函数?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我面对着名的可怕的钻石情况:

  A 
/ \
B1 B2
\ /
C
|
D

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



B1 B2 实际上派生自 A ,并有一些构造函数和一个 protected 默认构造函数。 p>


B1 B2的构造函数不调用 A 的默认构造函数。
[reedit]
B1 B2 的默认构造函数不调用默认 A 的构造函数。
[reedit]
[edit]



C 是一个抽象类,有一些构造函数不调用 A B1 B2 构造函数。



在类 D 中,我调用构造函数 A :: A C 的一些构造函数。



创建一个 A 来解决可怕的钻石问题,然后创建 B1 code>, B2 C 。因此,在 B1 B2 中没有调用 A 的默认构造函数c $ c>和 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 =>编译错误,如果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 是一个抽象类,它不能被实例化为一个完整的对象(但它可以被实例化为一个基类子对象,通过实例化a D )。
[edit]



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

解决方案

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



编译器不能预先知道实例化哪些类(这是一个运行时属性),并且不能知道在链接时间之前可能调用哪些构造函数。



标准文本(强调我的):


表示虚拟基类的所有子对象都由最大派生类(1.8 [intro.object])的构造函数
初始化。如果最大派生类的
构造函数没有为虚拟基类V指定
mem-initializer,则调用V的默认
构造函数来初始化虚拟基类子对象。
如果V没有可访问的默认构造函数,
初始化是不成形的。在任何不是最大派生类的
类的构造函数中,命名一个虚拟库
类的mem-initializer应该在执行期间被忽略


1)对于抽象类也不例外,只能解释为所有构造函数都应该做



2)它表示在运行时,这种尝试被忽略。



一些委员会成员在 DR 257



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

部分:12.6.2 [class.base.init]状态:CD2发布者:Mike
Miller日期:2000年11月1日[在2009年10月会议上投票到WP。]



抽象基类的构造函数必须为每个虚拟基类提供
mem-它是直接
还是间接派生?由于虚拟库
类的初始化由最派生的类执行,并且由于抽象的
基类不能是最多派生的类,因此似乎有
没有原因要求抽象基类的构造函数到
初始化虚拟基类。



从标准中还不清楚是否有这样的
需求或不。相关文本位于12.6.2
[class.base.init]段落6:


上面引用的)


此段仅要求最派生类的构造函数
具有虚拟基类的mem初始化器。如果沉默是
,被解释为允许不是
的类的构造函数允许忽略这些mem初始化器?


没有沉默。一般规则适用于没有抽象类的具体规则


Christopher Lester,在comp.std .c ++,2004年3月19日:如果你的任何一个
阅读这篇文章是发生在上述工作组的成员,
我想鼓励你审查其中包含
的建议,因为在我看来,提交的最后一次是
两者(a)正确(标准DOES的沉默任务的
省略)和(b)描述大多数用户将直觉地期望
和C ++语言的愿望。



建议是清楚的是,抽象
基类的构造函数不应该被要求提供初始化对于它们包含的任何
虚拟基类(因为只有最多派生类具有
初始化虚拟基类的作业,并且抽象基类
类不可能是最多派生类) 。


建议无法使更清楚的东西现在不存在。


$ b $

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


建议的解决方案(2009年7月)



(从段落11移动到12.6.2结束)
[class.base.init]段落7:



...每个基础的初始化并且成员构成
全表达式。 mem初始化程序中的任何表达式都被计算为执行初始化的full-expression的
部分。 A
mem-initializer其中mem-initializer-id命名一个虚拟基础
类在执行任何类的构造函数时被忽略,
不是最多的派生类。



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



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



如果实体是具有
大括号或初始值的非静态数据成员,则实体按照
8.5 [dcl.init];

$ b $中指定的方式初始化b

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



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



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



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



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



对于大多数派生类的构造函数,如下所述的
(1.8 [intro.object]),虚拟基类应该是
按它们出现在深度优先的顺序初始化
left从右到左遍历基类的有向非循环图
其中从左到右是派生类base-specifier-list中基类
名称的出现顺序。



然后,直接基类将在声明
中初始化,因为它们出现在base-specifier-list中(不管



然后,非静态数据成员将按照
的顺序初始化,它们在类定义中被声明了



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



[注意:声明顺序是强制的,以确保base和
成员子对象以与
初始化相反的顺序被销毁。 -end note]



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



代表虚拟基类的所有子对象都由最大派生类的构造函数(1.8 [intro.object])
初始化。如果最大派生类的
构造函数没有为虚拟基类V指定
mem-initializer,则调用V的默认
构造函数来初始化虚拟基类子对象。
如果V没有可访问的默认构造函数,
初始化是不成形的。在执行任何不是最大派生类的
类的构造函数时,应该忽略命名一个虚拟库
类的mem-initializer。 [示例:...


DR被标记为CD2:委员会同意这是一个问题,以解决此问题。


I face the well known "dreaded" diamond situation :

  A
 / \
B1 B2
 \ /
  C
  |
  D

The class A has, say the constructor A::A(int i). I also want to forbid a default instantiation of a A so I declare the default constructor of A as private.

The classes B1 and B2 are virtually derived from A and have some constructors and a protected default constructor.

[edit] The constructors of B1 and B2 don't call the default constructor of A. [reedit] The default constructors of B1 and B2 don't call the default constructor of A either. [reedit] [edit]

The class C is an abstract class and has some constructors that don't call any of the A, B1 or B2 constructors.

In the class D, I call the constructor A::A(i) and some constructor of C.

So as expected, when D is created, it first creates a A to solve the dreaded diamond problem, then it creates B1, B2 and C. Therefore there is no call of the default constructor of A in B1, B2 and C because if there was, it would create many instances of A.

The compiler rejects the code because the default constructor of A is private. If I set it to protected it compiles.

What I don't understand is that when I run the code, the default constructor of A is never called (as it should be). So why doesn't the compiler allow me to set it as private?

[edit] okay I'll write an example... but it hurts ;-)

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);
}

The compiler says that in constructor of C, A::A() is private. I agree with this, but as C is an abstract class, it can't be instantiated as a complete object (but it can be instantiated as a base class subobject, by instantiating a D). [edit]

I added the tag `language-lawer' on someone's recommendation.

解决方案

C++ doesn't have an access control specifier for member functions that can only be called from a derived class, but a constructor for an abstract class can only be called from a derived class by definition of an abstract class.

The compiler cannot know in advance exactly which classes are instantiated (this is a runtime property), and it cannot know which constructors are potentially called before link-time.

The standard text (emphasis mine):

All sub-objects representing virtual base classes are initialized by the constructor of the most derived class (1.8 [intro.object]). If the constructor of the most derived class does not specify a mem-initializer for a virtual base class V, then V's default constructor is called to initialize the virtual base class subobject. If V does not have an accessible default constructor, the initialization is ill-formed. A mem-initializer naming a virtual base class shall be ignored during execution of the constructor of any class that is not the most derived class.

1) It makes no exception for abstract classes and can only be interpreted as saying that all constructors should do a (sometimes fake) attempt at calling virtual base constructors.

2) It says that at runtime such attempts are ignored.

Some committee members have stated a different opinion in DR 257:

  1. Abstract base constructors and virtual base initialization

Section: 12.6.2 [class.base.init] Status: CD2 Submitter: Mike Miller Date: 1 Nov 2000 [Voted into WP at October, 2009 meeting.]

Must a constructor for an abstract base class provide a mem-initializer for each virtual base class from which it is directly or indirectly derived? Since the initialization of virtual base classes is performed by the most-derived class, and since an abstract base class can never be the most-derived class, there would seem to be no reason to require constructors for abstract base classes to initialize virtual base classes.

It is not clear from the Standard whether there actually is such a requirement or not. The relevant text is found in 12.6.2 [class.base.init] paragraph 6:

(...quoted above)

This paragraph requires only that the most-derived class's constructor have a mem-initializer for virtual base classes. Should the silence be construed as permission for constructors of classes that are not the most-derived to omit such mem-initializers?

There is no "silence". The general rule applies as there is no specific rule for abstract classes.

Christopher Lester, on comp.std.c++, March 19, 2004: If any of you reading this posting happen to be members of the above working group, I would like to encourage you to review the suggestion contained therein, as it seems to me that the final tenor of the submission is both (a) correct (the silence of the standard DOES mandate the omission) and (b) describes what most users would intuitively expect and desire from the C++ language as well.

The suggestion is to make it clearer that constructors for abstract base classes should not be required to provide initialisers for any virtual base classes they contain (as only the most-derived class has the job of initialising virtual base classes, and an abstract base class cannot possibly be a most-derived class).

The suggestion cannot make "clearer" something that doesn't exist now.

Some committee members are taken their desire for reality and it is very wrong.

(snip example and discussion similar to OP's code)

Proposed resolution (July, 2009):

Add the indicated text (moved from paragraph 11) to the end of 12.6.2 [class.base.init] paragraph 7:

...The initialization of each base and member constitutes a full-expression. Any expression in a mem-initializer is evaluated as part of the full-expression that performs the initialization. A mem-initializer where the mem-initializer-id names a virtual base class is ignored during execution of a constructor of any class that is not the most derived class.

Change 12.6.2 [class.base.init] paragraph 8 as follows:

If a given non-static data member or base class is not named by a mem-initializer-id (including the case where there is no mem-initializer-list because the constructor has no ctor-initializer) and the entity is not a virtual base class of an abstract class (10.4 [class.abstract]), then

if the entity is a non-static data member that has a brace-or-equal-initializer, the entity is initialized as specified in 8.5 [dcl.init];

otherwise, if the entity is a variant member (9.5 [class.union]), no initialization is performed;

otherwise, the entity is default-initialized (8.5 [dcl.init]).

[Note: An abstract class (10.4 [class.abstract]) is never a most derived class, thus its constructors never initialize virtual base classes, therefore the corresponding mem-initializers may be omitted. —end note] After the call to a constructor for class X has completed...

Change 12.6.2 [class.base.init] paragraph 10 as follows:

Initialization shall proceed proceeds in the following order:

First, and only for the constructor of the most derived class as described below (1.8 [intro.object]), virtual base classes shall be are initialized in the order they appear on a depth-first left-to-right traversal of the directed acyclic graph of base classes, where "left-to-right" is the order of appearance of the base class names in the derived class base-specifier-list.

Then, direct base classes shall be are initialized in declaration order as they appear in the base-specifier-list (regardless of the order of the mem-initializers).

Then, non-static data members shall be are initialized in the order they were declared in the class definition (again regardless of the order of the mem-initializers).

Finally, the compound-statement of the constructor body is executed.

[Note: the declaration order is mandated to ensure that base and member subobjects are destroyed in the reverse order of initialization. —end note]

Remove all normative text in 12.6.2 [class.base.init] paragraph 11, keeping the example:

All subobjects representing virtual base classes are initialized by the constructor of the most derived class (1.8 [intro.object]). If the constructor of the most derived class does not specify a mem-initializer for a virtual base class V, then V's default constructor is called to initialize the virtual base class subobject. If V does not have an accessible default constructor, the initialization is ill-formed. A mem-initializer naming a virtual base class shall be ignored during execution of the constructor of any class that is not the most derived class. [Example:...

The DR is marked "CD2": the committee agrees this was an issue and the language definition is changed to fix this issue.

这篇关于在抽象类构造函数中,为什么我需要调用一个永远不会调用的虚拟基址的构造函数?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆