《深入探索C++对象模型》读书笔记——第二章 构造函数语意学

第二章 构造函数语意学The Semantics of Constructors

1. Jerry Schwarz,iostream函数库建构师,曾为了让cin能够求得一个真假值,于是他为它定义了一个conversion运算符operator int()。但在语句cin << intVal中,其行为出乎意料:程序原本要的是cout而不是cin!但是编译器却找到一个正确的诠释:将cin转型为整型,现在left shift operator <<就可以工作了!这就是所谓的“Schwarz Error”。Jerry最后以operator void *()取代operator int()。
2. 引入关键词explicit的目的,就是为了提供程序员一种方法,使他们能够制止单一参数的constructor被当作一个conversion运算符。其引入是明智的,但其测试应该是残酷的!
2.1 Default Constructor的构建
1.global objects的内存保证会在程序激活的时候被清为0。local objects配置于程序的堆栈中,heap objects配置于自由空间中,都不一定会被清为0,它们的内容将是内存上次被使用后的遗迹。
2.“default constructors在需要的时候被编译器产生出来”。关键字眼是“在需要的时候”。是被编译器需要。
对于class X,如果没有任何user-declared constructor,那么会有一个default constructor被隐式声明出来。一个被被隐式声明出来的default constructor将是一个trivial(没啥用的)constructor……
3. 以下四种情况,编译器必须为未声明constructor的classes合成一个implicit nontrivial default constructor:
(1)带有default constructor的member class object
(2)带有default constructor的base class
(3)带有virtual function
(4)带有virtual base class
其它各种情况且没有声明任何constructor的classes,它们拥有的是implicit trival default constructors,它们实际上并不会被合成出来。
4、带有default constructor的member class object
(1)在各个不同的版本模块中,编译器避免合成出多个default constructor的方法如下:
把合成的default constructor、copy constructor、assignment copy operator都以inline方式完成。一个inline函数有静态链接,不会被档案以外者看到。如果函数过于复杂,不适合做成inline,就会合成一个explicit non-inline static实体。
(2)

被合成出来的Bar default constructor内含必要的代码,能够调用class Foo的default constructor来处理member object Bar::foo,但它并不产生任何代码来初始化Bar::str。
将Bar::foo初始化是编译器的责任,将Bar::str初始化则是程序员的责任。
被合成出来的default constructor只满足编译器的需要,而不是程序员的需要。为了让程序片段正确执行,字符指针str也需要被初始化。
编译器会扩张已存在的constructor,在其中安插一些代码,是的user code被执行之前,先调用必要的default constructors。

(3)如果有多个class member objects都要求constructor初始化操作,C++语言要求以“member objects在class中的声明顺序”来调用各个constructors。

5、带有default constructor的base class
如果一个没有任何constructor的class派生自一个“带有default constructor”的base class,那么这个derived class的default constructor会被视为nontrivial,并因此需要被合成出来。它将调用上一层base classes的default constructor(根据他们的声明顺序)。
6、带有一个Virtual Function的class
有两种情况,也需要合成出default constructor
(1)class声明(或继承)一个virtual function
(2)class 派生自一个继承串链,其中有一个或更多的virtual base classes。
由于缺乏由user声明的constructors,编译器会详细记录合成一个default constructor的必要信息。

此外,widget.flip()的虚拟调用操作会被重新改写,以使用widget的vptr和vtbl中的flip()条目:
(*widget.vptr[1])(&widget)
其中:(1)1表示flip()在virtual table中的固定索引
(2)&widget代表要交给“被调用的某个flip()函数实例”的this指针。
7、带有一个virtual base class的class

必须使virtual base class在每一个derived class object中的位置,能够于执行期准备妥当。
所有经由reference或pointer来存取一个virtual base class的操作都可以通过相关指针来完成。

其中_vbcX表示编译器所产生的指针,指向virtual base class X
_vbcX(或编译器所做出的某个东西)是在class object构造期间被完成的。对于class所定义的每一个constructor,编译器会安插那些“允许每一个virtual base class的执行器存取操作”的代码。如果class没有声明任何constructors,编译器必须为它合成一个default constructor。

总结:
有四种情况会造成“编译器必须为声明constructor的classes合成一个default constructor”。把这些合成物称为implicit nontrivial default constructors。被合成出来的constructor只能满足编译器(而非程序)的需要。
没有存在那四种情况而又没有声明任何constructor的classes,我们说他们拥有的是implicit trivial default constructors,他们实际上不会被合成出来。
在合成的default constructor中,只有base class subobjects和member class objects会被初始化。所有其他的nonstatic data member(如整数、整数指针、整数数组等)都不会被初始化。这些初始化操作对程序而言或许有需要,但对编译器则非必要。

新手误解:
(1)任何class如果没有定义default constructor,就会被合成出一个来。
(2)编译器合成出来的default constructor会显式设定“class内每一个data member的默认值”。
——都不对。

8、编译器合成implicit nontrivial default constructor,不过是暗地里作了一些重要的事情以保证程序正确合理地运行。如果程序员提供了多个constructors,但其中都没有default constructor,编译器同样会在这些constructors中插入一些相同功能的代码,这些代码都将被安插在explicit user code之前。

2.2 Copy Constructor的构造操作
0、一个class object可以从两种方式复制得到:初始化和指定,从概念上而言,这两个操作分别是以copy constructor和copy assignment operator完成的。
1、Default memberwise initialization
如果class没有提供一个explicit copy constructor又当如何?当class object以“相同class的另一个object”作为初值,其内部是以所谓的default memberwise initialization首发完成的,也就是把每一个内建的或派生的data member的值,从某一个object拷贝一份到另一个object身上。不过他并不会拷贝其中的member class object,而是以递归的方式施行memberwise initialization。
Default constructors和copy constructors在必要的时候才由编译器产生出来。“必要”是指当class不展现bitwise copy semanics时。
决定一个copy constructor是否为trival的标准在于class是否展现出所谓的“bitwise copy semanics”。
2、Bitwise Copy Semanics(位逐次拷贝)

这种情况并不需要一个default copy constructor,因为上述声明展现了“default copy semanics”

这种情况下,编译器必须合成出一个copy constructor,以便调用member class string object的copy constructor:

3.不要 “bitwise copy semantics”
以下四种情况,一个class不展现bitwise copy semantics:
(1)class内含一个member object而后者的class声明有或被编译器合成有一个copy constructor时;
(2)class继承自一个base class而后者存在或被编译器合成有一个copy constructor时;
(3)当class声明了一个或多个virtual functions时;
(4)当class派生自一个继承串链,其中有一个或多个virtual base classes时。
前两种情况中,编译器必须将member或base class的copy constructors调用操作安插到被合成的copy constructor中。
4、重新设定virtual Table的指针

(1)一旦一个class object中必须引入vptr,编译器就必须为它的vptr正确地设置好初值。此时,该class就不再展现bitwise semantics。
(2)当一个base class object以其derived class object内容作初始化操作时,其vptr复制操作必须保证安全。
5、处理virtual Base Class Subject
在每一个编译器对于虚拟继承的支持承诺,都代表必须让“derived class object中的virtual base class subobject位置”在执行期就准备妥当。维护位置的完整性是编译器的责任。

2.3 程序转化语意学
1. 每一个明确的初始化操作都会有两个必要的程序转化阶段:先重写每一个定义,剥除其中的初始化操作,然后安插class的copy constructor调用操作。
2. 把一个class object当作参数传给一个函数或是作为一个函数的返回值,相当于以下形式的初始化操作:
X xx = arg; 其中xx代表形式参数或返回值,而arg代表真正的参数值。
3. 函数定义如下:X bar(){X xx; return xx;},bar()的返回值通过一个双阶转化从局部对象xx中拷贝出来:
ü 首先为bar添加一个额外参数,类型是class object的一个reference,这个参数用来放置被拷贝构建而得的返回值。
ü 然后在return指令之前安插一个copy constructor调用操作,以便将欲传回之object的内容当作上述新增参数的初值,同时重写函数使它不返回任何值。
4. Named Return Value(NRV)优化如今被视为是标准C++编译器的一个义不容辞的优化操作,它的特点是直接操作新添加的额外参数。注意只有copy constructor的出现才会激活C++编译器的NRV优化!NRV优化虽然极大地改善了效率,但还是饱受批评:一是优化由编译器默默完成,而是否完成以及其完成程度完全透明;二是一旦函数变得比较复杂,优化就变得较难施行;三是优化由可能使程序产生错误——有时并不是对称地调用constructor和destructor,而是copy constructor未被调用!
5. 在编译器提供NRV优化的前提下,如果可以预见class需要大量的memberwise初始化操作,比如以by value的方式传回objects,那么提供一个explicit inline copy constructor的函数实体就非常合理。此种情况下,没有必要同时提供explicit assignment operator定义。
6. copy constructor的应用迫使编译器多多少少对程序代码作部分优化,尤其是当一个函数以by value的方式传回一个class object,而该class有一个copy constructor(或定义或合成)时,无论在函数的定义还是在使用上将导致深奥的程序转化。此外,编译器将实施NRV优化。
7. 注意正确使用memset()和memcpy(),它们都只有在classes不含任何由编译器产生的内部members如vptr时才能有效运行!

2.4 成员初始化列表
1. 当写下一个constructor时,就有机会设定class members的初值。不是经由member initialization list,就是在constructor函数本身之内。
2. 下列情况,为了让程序能被顺利编译,必须使用member initialization list:
ü 初始化一个reference member时;
ü 初始化一个const member时;
ü 调用一个base class的constructor,而它拥有一组参数时;
ü 调用一个member class的constructor,而它拥有一组参数时。
3. 编译器会对initialization list一一处理并可能重新排序,以反映出members的声明次序,它会安插一些代码到constructor内,并置于任何explicit user code之前。
4. 一个忠告:请使用“存在于constructor体内的一个member”,而不是“存在于member initialization list中的一个member”,来为另一个member设定初值。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值