基本语言细节--《深度探索C++对象模型》--(2)The Semantics of Constructors--总结点

                       基本语言细节--《深度探索C++对象模型》--(2)The Semantics of Constructors--总结点

1.编译器在何时会产生nontrivial default constructor?

   (1).带有Default Constructor 的成员类对象;

        类中的某成员变量是一个类;

        如果有多个类成员都要求用constructor 初始化操作,那么C++将按照其在类中声明的顺序,依次调用各个的constructor.这些代码由编译器完成插入,并且被安插在explicit user code 之前!(无继承的情形下)

   (2). 带有Default Constructor的Base Class;

         先调用基类,再派生类。

   (3).带有一个virtual function的类;

       1.类中声明或者继承了一个虚函数;2.类继承了个虚基类;此种默认构造函数之所以是有用的,是因为,C++在支持虚函数机制时,会使用虚函数表,并在对象内部安插一个虚函数表指针vfptr,这种默认的构造函数就是为了在其内部完成对vfptr的正确初始化。这是编译器的责任!!!因为,唯有这样才能正确的提供多态机制;

   (4).带有一个Virtual Base Class 的Class;

       虚基类的实现虽然在各个编译器之间存在较大的差异,但是每一种实现的共同之法,是在于必须使虚基类在每一个派生类中的位置在执行期就准备妥当。比如cfront做法是靠“在每一个派生类对象内安插一个指针”来完成的,所有经由reference或pointer 来存取一个虚基类的操作都可以通过相关的指针来完成—_vbcX,其表示为编译器所产生的指针,指向虚基类。故而,在没有构造函数时,编译期必须合成一个构造函数,用已允许每一个虚基类执行期间的存取操作。

    总结:1.除了上述四种情况而又没有声明任何构造函数的类,我们称它拥有implict trivial default constuctors,它们实际上不会被合成出来;2.在合成的默认构造函数中,只有基类的对象部分以及成员对象会被初始化,而其他的非静态成员(如整数,指针等)并不会被初始化。因为前面的初始化是对编译器必要的,而后面的是对程序必要的,各个责任是明确的!!!


2.何时会调用Copy Constructor?

   (1).对一个对象进行显式的初始化操作;

   (2).对象被当做一个参数传递给某个函数;

   (3).当函数返回一个对象时。

    与默认的构造函数一样,如果类没有声明一个复制构造函数,那么,就会有隐式的声明和定义,同时,C++标准,也区分nontrivial 和trivial,其是否为trivial(没啥用的)标准在于class是否展现出“位逐次拷贝”的语义(就是仅仅对内建成员,类中不包含任何由编译器产生的内部成员时,才展现出的语义)。如果展现出,那么这个copy constructor 就是trivial。

3.什么时候不是类不展现出“位逐次拷贝”的语义?------Bitwise Copy Semantics

   (1).当一个类中的成员对象具有一个copy constructor时;

   (2).当类继承的基类具有一个显式的或者是隐式的copy constructor 时;

   (3).当类中声明了virtual function 时;

   (4).当类派生自一个继承串链时,其中一个或者是多个具有虚基类时。

   总结:1.针对(1)(2),编译器必须把成员和基类的copy constructor 调用操作,安插到被合成的copy constructor 中,这是编译器的责任;

              2.由于存在虚函数的类对象都会有一个虚函数表指针,当派生类对象赋值给基类对象时,由于发生切割行为,而致使虚函数表指针,不只是简单的位拷贝,而应该由编译器来正确设定赋值后的基类虚函数表指针值,故而保证切割后的基类对象只能调用其本身的虚函数,而不再有多态!这也是基类对象不能实现多态,而必须由引用或指针才能实现这种机制所要求的。

              所以为了正确设定vfptr的值,按位逐次拷贝语音失效,必须启用copy constructor来完成。

              3.由于虚拟基类的存在需要特殊处理,基于之前谈论的原因,其位置必须要在执行期就准备妥当。而按位逐次拷贝可能破坏这个位置,此位置的维护是编译器的责任,故而编译器必须合成copy constructor 来完成正确的初始化工作。

              之上所谈论的问题,不会出现在,同一个类对象间的赋值中,而是出现在一个基类对象以一个派生类对象赋初值的时候,会牵涉到由对象切割而带来的虚函数表指针以及虚基类指针的正确初始化问题!!!此时由编译器产生的copy constructor 是有用的,也是必须的!!!号称nontrivial。

4.Program Transformation Semantics ---程序转化语意学

    (1)显式的初始化操作,对编译器会对函数进行改写

     比如:  X x0;    void eat(){   X x1(x0);   X x2=x0;   X x3=X(x0);}

      经过改写后,程序成如下:void eat(){  X x1;X x2;X x3;       x1.X::X(x0);x2.X::X(x0);x3.X::X(x0);}

经过了重写每一个对象的定义,初始化操作被剥除,类的copy constructor 调用操作被安插进去。

    (2).参数的初始化----注意编译器的改写行为;

   比如:函数;void eat (X x0),。调用方式为X xx;void eat (X xx);    编译器改写为X temp; temp.X::X(xx); x0=temp;void eat(X & x0);(具体内容见书P63)

    (3).返回值的初始化------编译器会构建一个额外参数,来取代返回值,并将有返回值的函数改写为void。

5. 在哪个层次对返回值进行优化?

   (1).使用者层面做优化:

   由构造函数来实现copy constructor 的工作,即是特殊计算用途的构造函数,但是其缺点是会造成类扩大。这种是以效率为优先,而抽象化次之,因为其减少了copy constructor 的调用。

   (2).在编译器层面做优化--NRV(Named Rerurn Value)

   主要还是利用编译器添加一个额外的参数,直接利用copy constructor 构造对象,然后直接处理这个对象,而函数被改写为void,而要想实现NRV优化,必须有一个copy constructor。

   总结:一般而言,面对一个对象作为另一个对象的初值时,语言允许编译器有大量的自由发挥的空间,其利益当然是导致机器码产生时有明显的效率提升,其缺点时,你不能安全规划copy constructor 的副作用,必须视其执行而定!

6.何时必须使用成员初始化列表---Member Initialization List

   (1).初始化一个引用成员时;

   (2).初始化一个常量成员时;

   (3).当有基类且其构造函数带有一组参数时

   (4).当有成员对象的构造函数带有一组参数时;-----放在成员初始化列表中,可以大大减少编译器为完成初始化,而自动添加的代码(如果放在构造函数内,会有临时对象的产生与销毁,这会有大量的没用的代码而放在列表中,便可以直接调用构造函数)。

      故而,最好是将所有的初始化都放在成员初始化列表中。(这是一种良好的编程风格!)

7.成员初始化列表是如何工作的?

   (1).list中的项目的初始化顺序是由成员变量的声明顺序决定的,而不是由成员初始化列表的顺序决定。

   (2).成员初始化列表中的项目代码会被编译器安插到 “构造函数用户显式初始化代码”之前!

 总结:简略的说,编译器会对initialization list 一一处理并可能重排顺序,以反映出成员变量的声明顺序。它会安插一些代码到构造函数内,并置于任何Explict user code 之前!!!


卧浪居士 于HUST2013.11.20

   

 





评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值