为什么不能在子类的初始化列表里初始化父类的成员

来源:互联网 发布:数据出售 编辑:程序博客网 时间:2024/05/05 19:14
好几次遇到此类问题了:便不得不找些资料弄明白它;
具体代码是:
2
3
4
5
6
7
8
9
10
class A {
protected:
    int n_;
};
 
class B : public A {
public:
    B() : n_(0)
    {}
};
这是简化的,作为分析问题的。
然后无情的报错:
         |error: class 'B' does not have any field named 'n_'

不解,瞪了几秒钟后以为是access level的问题,于是把protected改成了public,但是问题依旧。

又瞪了一段时间才反应过来刚才脑残了,并不是由access level导致的问题。

这个问题的本质是:子类的初始化列表不能初始化父类或者祖先类的成员

这是标准规定的,至于为什么会有这样一个规定,以及上述的奇怪现象,一个可以参考的解释如下

1)首先是初始化列表的作用

初始化列表其实是一种后天强加的初始化语义。

编译器处理后,会把初始化列表的内容先转化,然后插入到构造函数的开头,之后的内容才是你在构造函数里写的语句,如果你有写的话。

但是,这两部分是截然不同的语义:前者是编译器插入的初始化语句,且开始执行用户自己的语句时,编译期要保证所有需要初始化的成员都已经初始化了,这也是各大书籍推荐使用初始化列表显式初始化成员的原因。

2)继承情况下的初始化顺序

对应一个基类在上的继承树,一个子类对象的初始化顺序是自顶向下

子类对象的构造函数会首先利用父类的构造函数创建一个父类对象,然后再父类对象的基础之上再把自己创建出来。(想象一个递归调用栈或者后序遍历)

所以,在子类利用构造函数初始化的时候,其父类对象已经是确定构造完毕的

3)标准要求,每个对象在其生命周期内只能被初始化一次. 这是一个非常显然的要求

所以,如果我们在子类的初始化列表中对父类成员进行初始化,那么在子类构造函数开始时,这个对象已经可能被父类构造函数初始化了(内建类型需要显式初始化,带有Non-trivial默认构造的函数就算不指定也会被初始化),因此此时如果子类在初始化,就违反了上述要求3)。

那么这里有个问题:编译器能否检查父类的对象是否已经被初始化,如果是则提示,不是则编译通过?

我个人觉得是完全可以的,intellisense甚至都可以做到。但是如果允许这种行为的话,可能会出现,当你从一个类继承时,你需要沿着继承链向上,判断你需要初始化的父类成员是否已经被他的某个子孙类给初始化了。这显然不是一种好的做法。

而且无论从哪个设计角度,子类初始化父类成员这种越俎代庖的行为都不合理。

至于解决方案,可以对父类的构造函数传参数对其进行初始化。或者结合1)和2)可以推出,在构造函数体里内直接赋值也是可以的


0 0