《inside the cpp object model》 阶段性阅读总结(2)

终于阅读完了第二章。。。这回先做个中文的总结,然后再慢慢翻译吧。。。
第二章 构造函数的语义
章前阅读:
c++编译器会在程序员的背后做一些事情。并且编译器会尽量修改一个不合法的语句,使其合法,而不是直接标志该程序是错误的。书中举的例子是cin。Jerry Schwarz(iostream库的作者)曾经有过这么一个故事:他为了使if(cin)合法,于是为cin添加了operator int();但是,当偶用户错误的使用cin<<intVel的时候(正确的是cin>>intVel),编译器不会报错,而是偷偷的调用tmp = cin.operator int();然后<<解释为左移运算符。(最终Jerry将int()改为了void*(),从而避免这个问题)。
第一节 默认构造函数的构造过程
编译器会在需要的地方自动生成默认构造函数,这个“需要”很关键,是程序需要,而不是程序员需要。程序需要的意思是没有这个默认构造函数,程序将无法正确运行。
如下情形属于程序需要:
如果一个类里没有任何构造函数,但是包含一个“带有默认构造函数”的对象成员的话,隐式的默认构造函数将被添加。如果一个类里已经包含了构造函数,编译器是不能自动添加构造函数的,此时,编译器会扩大现有的构造函数,将初始化该对象成员的代码添加到用户代码之前。如果有多个动向成员需要被初始化,编译器会按照他们的声明顺序添加构造代码。
如果本类继承自一个包含默认构造函数的基类。编译器会增加默认构造函数或者扩大现有的每个构造函数来调用每一个基类的构造函数,调用顺序与声明顺序一致。在随后的派生类里,这个被添加的构造函数看上去和显示提供的默认构造函数没有不同。
如果本类带有虚函数,或者这个类派生自一个带有虚基类的继承链。
对c++常见的误解:
默认构造函数会在所有没有定义他的类里自动生成。
编译器生成的默认构造函数会初始化所有的数据成员。
第二节 拷贝构造函数的构造过程
本节讨论了三种情况,一是用一个对象去初始化另一个对象,二是将对象作为函数参数,三是将对象作为函数的返回值。
如果定义了拷贝构造函数的话,编译器会自动调用拷贝构造函数,但是如果没有显示的提供一个拷贝构造函数的话,编译器会通过默认对象初始化来初始化该对象,这个方式会直接拷贝每一个内置或者派生数据成员(比如指针或数组)的值。但是类成员对象并没有被拷贝,而是递归的去调用对象初始化。
同构造函数,默认拷贝构造函数会在编译器需要的地方被添加。
如果一个类展示出了位拷贝语义的话,默认拷贝构造函数不会被添加。
有四种情况,类不会展示位拷贝语义:
该类包含一个“带有拷贝构造函数”的对象成员。(无论是编译器添加还是程序员自己定义的)
该类继承自一个带有拷贝构造函数的基类。
该类声明了一个或多个虚函数。如果带有虚函数的话,需要一个拷贝构造函数来使vptr被正确的初始化。
该类派生自带有一个或多个虚基类的继承链。
第三节 程序变换语义
所有的显示初始化会被转义为两步:
重写为不带初始化的定义。
添加拷贝构造函数。
所有的传值参数的初始化也会被转义:
定义临时变量。
拷贝构造函数初始化该临时变量。
函数参数变为引用类型。
传递该临时变量。
所有的返回值也会被转义:
函数参数表中添加一个引用类型。
调用该引用类型的拷贝构造函数。
使用逗号表达式来使得该引用被调用。(function(X& __result),__result)
讨论NRV(named return value)这一个优化备受争议,也有些难度,大概意思就是编译器消除一些拷贝无关紧要的拷贝构造函数调用(消除之后看起来对程序无丝毫影响)。但是有的时候看似无影响的消除却会影响到程序的运行。
第四节 成员初始化列表
作者在开篇强调什么时候使用初始化列表是很重要的。
初始化列表会在构造函数体的用户代码前添加代码来进行初始化。
人们经常在什么时候使用初始化列表呢:
当需要初始化一个引用类型
当需要初始化一个常量
当需要调用含参的基类或者成员对象的构造函数
为什么说时候使用初始化列表是很重要的呢:
因为初始化列表的调用顺序和声明顺序一致,滥用初始化列表可能导致错误的初始化顺序。
如果通过本类的函数的返回值对成员进行初始化的话,可能会出现如下情况。该成员是本类的基类。于是X::X(function())调用就会被改为X::X(this,this->function())。此时的this可能会指向基类而不是子类!
posted @ 2011-04-19 16:53  dk647  阅读(1124)  评论(2编辑  收藏  举报