• 【C++对象模型】第二章 构造函数语意学


    1、Default Constructor

      当编译器需要的时候,default constructor会被合成出来,只执行编译器所需要的任务(将members适当初始化)。

    1.1  带有 Default Constructor 的Member Class Object

      编译器的出来是:如果一个class A 内含一个或者一个以上 member class objects ,那么class A 的每一个 constructor 必须调用每一个member classes 的default constructor 。编译器会扩张已存在的constructors,在其中安插一些代码,使得 user code在被执行之前,先调用(调用顺序一member objects在class 的声明次序一致)必要的 default constructors。(例如:一个类中包含了两个成员,一个成员是一个对象,而另一个成员是built-in的变量,那么编译器会调用这个member对象的默认构造器,built-in变量就会置之不理。)

      注意:这样的编译器默认合成构造器,是隐式的,如果类本身存在构造器,那么编译器会在程序需要的(可以理解为user code )代码之前,安插必要的default constructor。

      举个例子,假设我们有以下三个classes:

    class Dopey {public: Dopey();...};
    class Sneezy {public: Sneezy(int); Sneezy();...};
    class Bashful {public: Bashful();...};

      以及一个class Snow_White:

    class Snow_White
    {
    public:
        Dopey dopey; 
        Sneezy sneezy;
        Bashful bashful;
    private:
        int mumble;
    };

      如果Snow_White没有定义default constructor,就会有一个nontrivial constructor被合成出来,依序调用Dopey、Sneezy、Bashful的default constructors。然而如果Snow_White定义了下面这样的default constructor:

    //程序员所写的default constructor
    Snow_White::Snow_White(): sneezy(1024)
    {
        mumble = 2048;
    }

      它将会被扩张为:

    //编译器扩张后的default constructor
    Snow_White::Snow_White():sneezy(1024)
    {
        //插入member class object
        //调用其constructor
        dopey.Dopey::Dopey();
        sneezy.Sneezy::Sneezy(1024);
        bashful.Bashful::Bashful();
    
        //expilict user code
        mumble = 2048;
    }

    1.2 带有 Default Constructor 的 Base class

      如果没有定义构造函数,编译器会产生一个nontrivial constructor,先调用相应的base class's constructor,如果用户自己定义了constructor,那么编译器将会安插调用所必要的default constructors的程序代码到用户自己定义的constructor中。(通俗地讲:用户已经定义了构造函数,但是没有定义默认构造函数,那么编译器会把默认的东西加在进去,但不会重新写一个default constructor出来)。

    1.3 带有一个 Virtual Function 的Class

      下面两种情况同样需要合成default constructor:

    • class 声明(或继承)一个 virtual function。
    • class派生自一个继承串链,其中一个或者更多的 virtual base class。

      扩展(constructor)操作会在编译期间发生:

    • 一个virtual function table 会被编译器产生出来,内放class 的virtual functions 的地址。
    • 在每一个 class object 中,一个额外的pointer member(vptr)会被编译器合成出来,内含相关的class vtbl的地址。

    1.4 带有一个 Virtual Base Class 的class

      Virtual base class的实现法在不同编译器之间有很大差异,然而,每一个实现的共同点在于必须使 virtual base class 在其每一个 derived class object中的位置,能够在执行期准备妥当。对于class所定义的每一个constructor 编译器都会安插那些“允许每一个virtual base class 的执行期存取操作”的码。

    1.5 总结

      以上四种情况,会导致“编译器必须为未声明constructor 的class 合成一个default constructor ”,这只是编译器(而非程序)的需要。它之所以能够完成任务,是借着“调用member object 或base class的default constructor ”或是“为每一个object初始化其 virtual function 机制或virtual base class 机制”完成。至于没有存在这四种情况而又没有生命constructor的class 实际上是不会被合成出来的。

      在合成的default constructor 中,只有base class subobjects(子对象)和member class objects会被初始化。所有其他的nonstatic data member ,如整数,整数指针,整数数组等是不会被初始化的,这些初始化操作对程序是必须的,但对编译器则并非需要的。

      C++新手一般有两个误解:

    • 任何class 如果没有定义default constructor ,就会被合成出来一个。
    • 编译器合成出来的default constructor 会明确设定 class 内每一个data member的默认值。

    2、Copy Constructor

    2.1 拷贝情况

      有三种情况,会以一个object的内容作为另一class object的初值。

      1.最明显的当然是对一个object做明确的初始化操作。

      2.当object被当做参数交给某个函数

      3.当函数返回一个class object。

      这三种情况需要有 copy constructor。

    2.2 Default Memberwise Initialization

      如果class 没有提供一个 explicit copy constructor(显示拷贝构造函数)时,当class object以“相同的另一个object作为初值是,其内部是以所谓的default memberwise initialization方式完成的。也就是把每一个内建的或派生的 data member(例如一个数组或指针)的值,从某个object拷贝一份到另一个object上,但不拷贝其具体内容。例如只拷贝指针地址,不拷贝一份新的指针指向的对象,这也就是浅拷贝,不过它并不会拷贝其中member class object,而是以递归的方式实行memberwise initialization。

      这种递归的memberwise initialization是如何实现的呢?

      答案就是Bitwise Copy Semantics和default copy constructor。如果class展现了Bitwise Copy Semantics,则使用bitwise copy(bitwise copy semantics编译器生成的伪代码是memcpy函数),否则编译器会生成default copy constructor。

      那什么情况下class不展现Bitwise Copy Semantics呢?

      有四种情况:

      1.当class内含有一个member class object,而这个member class 内有一个默认的copy 构造函数[不论是class设计者明确声明,或者被编译器合成]

      2.当class 继承自 一个base class,而base class 有copy构造函数[不论是class设计者明确声明,或者被编译器合成]

      3.当一个类声明了一个或多个virtual 函数

      4.当class派生自一个继承串链,其中一个或者多个virtual base class

      下面我们来理解这四种情况为什么不能使用bitwise copy,以及编译器生成的copy constructor都干了些什么。

      在前2种情况下,编译器必须将member或者base class的“ copy constructor的调用操作”安插到被合成的copy constructor中。

      第3种情况下,因为class 包含virtual function, 编译时需要做扩张操作:

      1.增加virtual function table,内含有一个有作用的virtual function的地址;

      2.创建一个指向virtual function table的指针,安插在class object内。

      所以,编译器对于每一个新产生的class object的vptr都必须被正确地赋值,否则将跑去执行其他对象的function了,其后果是很严重的。因此,编译器导入一个vptr到class之中时,该class 就不在展现bitwise semantics,必须合成copy Constructor并将vptr适当地初始化。

    2.3 处理Virtual Base Class Subobject

      virtual base class的存在需要特别处理。一个class object 如果以另一个 virtual base class subobject那么也会使“bitwise copy semantics”失效。

      每一个编译器对于虚拟继承的支持承诺,都是表示必须让“derived class object 中的virtual base class subobject 位置”在执行期就准备妥当,维护“位置的完整性”是编译器的责任。Bitwise copy semantics 可能会破坏这个位置,所以编译器必须自己合成出copy constructor。

      这也就是说,拷贝构造函数和默认构造器一样,需要的时候会进行构建,而并非程序员不写编译器就帮着构建。

    2.4 初始化列表

      下面四种情况必须使用初始化列表来初始化class 的成员:

      1.当初始化一个reference member时;

      2.当初始化一个const member时;

      3.当调用一个base class 的 constructor ,而它拥有一组参数(其实就是自定义的构造函数)时;

      4.当调用一个 member class 的 constructor,而它拥有一组参数时。

      这里总的需要留意两点:

      第一个:member object的初始化,最好放到初始化列表里面。若放置于构造器中,则会产生临时的object0,初始化之,在做赋值运算给object,然后object0自行销毁,期间耗时耗力。若置于初始化列表,则编译器会在构造函数中,user code之前,调用object的构造函数,予以初始化。   

      第二个:初始化列表的初始化次序。初始化次序和member在类中的声明次序一致。相互关联的member,需要十分留意初始化列表中,其中依赖的次序。解决的办法:把其中一部分使用初始化列表初始化,而另一部分放置到构造函数中使用user code予以表达,这样即便次序存在依赖,也会只“先执行合成的,再执行user的code”。

  • 相关阅读:
    Java进阶知识查漏补缺06
    SQL学习记录(concat)
    Restful API学习
    git学习
    获得xmlhttp对象
    vue-cli初接触
    vue初接触
    java使用百度UNIT
    JSON学习
    通用Mapper警告:建议修改基本类型为对应的包装类型!
  • 原文地址:https://www.cnblogs.com/ChinaHook/p/6753905.html
Copyright © 2020-2023  润新知