为什么不能在子类的初始化列表里初始化父类的成员
2015-11-13 12:12
295 查看
前段时间写了一段代码,其等价形式如下:
intellisense会在高亮行提示:
“n_” is not a nonstatic data member or base class of class “B”
不解,瞪了几秒钟后以为是access level的问题,于是把
又瞪了一段时间才反应过来刚才脑残了,并不是由access level导致的问题。
这个问题的本质是: 子类的初始化列表不能初始化父类或者祖先类的成员 。
这是标准规定的,至于为什么会有这样一个规定,以及上述的奇怪现象,一个可以参考的解释如下。
初始化列表其实是一种 后天强加 的初始化语义。
编译器处理后,会把初始化列表的内容先 转化 ,然后插入到构造函数的开头,之后的内容才是你在构造函数里写的语句,如果你有写的话。
但是,这两部分是截然不同的语义:前者是编译器插入的初始化语句,且开始执行用户自己的语句时,编译期要保证所有需要初始化的成员都已经初始化了,这也是各大书籍推荐使用初始化列表显式初始化成员的原因。
对应一个基类在上的继承树,一个子类对象的初始化顺序是 自顶向下 。
子类对象的构造函数会首先利用父类的构造函数创建一个父类对象,然后再父类对象的基础之上再把自己创建出来。(想象一个递归调用栈或者后序遍历)
所以,在子类利用构造函数初始化的时候,其父类对象已经是 确定 构造完毕的
3)标准要求,每个对象在其生命周期内只能被初始化 一次 . 这是一个非常显然的要求
所以,如果我们在子类的初始化列表中对父类成员进行初始化,那么在子类构造函数开始时,这个对象已经可能被父类构造函数初始化了(内建类型需要显式初始化,带有Non-trivial默认构造的函数就算不指定也会被初始化),因此此时如果子类在初始化,就违反了上述要求3)。
那么这里有个问题:编译器能否检查父类的对象是否已经被初始化,如果是则提示,不是则编译通过?
我个人觉得是完全可以的,intellisense甚至都可以做到。但是如果允许这种行为的话,可能会出现,当你从一个类继承时,你需要沿着继承链向上,判断你需要初始化的父类成员是否已经被他的某个子孙类给初始化了。这显然不是一种好的做法。
而且无论从哪个设计角度,子类初始化父类成员这种越俎代庖的行为都不合理。
至于解决方案,可以对父类的构造函数传参数对其进行初始化。或者结合1)和2)可以推出,在构造函数体里内直接赋值也是可以的。
class A { protected: int n_; }; class B : public A { public: B() : n_(0) {} };
class A { protected : int n_ ; } ; class B : public A { public : B ( ) : n_ ( 0 ) { } } ; |
“n_” is not a nonstatic data member or base class of class “B”
不解,瞪了几秒钟后以为是access level的问题,于是把
protected改成了
public,但是问题依旧。
又瞪了一段时间才反应过来刚才脑残了,并不是由access level导致的问题。
这个问题的本质是: 子类的初始化列表不能初始化父类或者祖先类的成员 。
这是标准规定的,至于为什么会有这样一个规定,以及上述的奇怪现象,一个可以参考的解释如下。
1)首先是初始化列表的作用
初始化列表其实是一种 后天强加 的初始化语义。编译器处理后,会把初始化列表的内容先 转化 ,然后插入到构造函数的开头,之后的内容才是你在构造函数里写的语句,如果你有写的话。
但是,这两部分是截然不同的语义:前者是编译器插入的初始化语句,且开始执行用户自己的语句时,编译期要保证所有需要初始化的成员都已经初始化了,这也是各大书籍推荐使用初始化列表显式初始化成员的原因。
2)继承情况下的初始化顺序
对应一个基类在上的继承树,一个子类对象的初始化顺序是 自顶向下 。子类对象的构造函数会首先利用父类的构造函数创建一个父类对象,然后再父类对象的基础之上再把自己创建出来。(想象一个递归调用栈或者后序遍历)
所以,在子类利用构造函数初始化的时候,其父类对象已经是 确定 构造完毕的
3)标准要求,每个对象在其生命周期内只能被初始化 一次 . 这是一个非常显然的要求
所以,如果我们在子类的初始化列表中对父类成员进行初始化,那么在子类构造函数开始时,这个对象已经可能被父类构造函数初始化了(内建类型需要显式初始化,带有Non-trivial默认构造的函数就算不指定也会被初始化),因此此时如果子类在初始化,就违反了上述要求3)。
那么这里有个问题:编译器能否检查父类的对象是否已经被初始化,如果是则提示,不是则编译通过?
我个人觉得是完全可以的,intellisense甚至都可以做到。但是如果允许这种行为的话,可能会出现,当你从一个类继承时,你需要沿着继承链向上,判断你需要初始化的父类成员是否已经被他的某个子孙类给初始化了。这显然不是一种好的做法。
而且无论从哪个设计角度,子类初始化父类成员这种越俎代庖的行为都不合理。
至于解决方案,可以对父类的构造函数传参数对其进行初始化。或者结合1)和2)可以推出,在构造函数体里内直接赋值也是可以的。
相关文章推荐
- JS总结
- linux的/dev/null 2>&1 &含义
- Masonry介绍与使用实践:快速上手Autolayout
- [LintCode] Minimum Adjustment Cost
- 汉语拼音转换工具(Python 版)
- jquery事件触发例子
- redis学习资源汇总
- item 3: 理解decltype
- 在网页中添加分享到微信、QQ、微博
- Fresco正传(3):DraweeHierarchy分析
- 数据库基本问题1
- mysqld(服务器)命令行选项
- python字符串编码报错解决
- git remote rename
- Linux 1>/dev/null 2>&1的含义
- ThinkPHP去除url中的index.php
- dsym文件分析工具
- 设计模式 状态模式 以自动售货机为例
- android编程之XML文件解析方法详解(附源码)
- CSS3新属性(上菜中.....)