• explicit的意义是让程序员能制止”单一参数的constructor”被当作conversion运算符

default constructor

  • default constructor只有在被编译器需要时,才会被合成出来,且合成出的constructor只执行编译器所需要的行动(并不对成员初始化)

含有default constructor的member class object

  • 在c++各个不同的编译模块(文件)中,编译器将合成的default constructor、copy constructor、destructor、assignment copy operator都用inline方式完成来避免合成多个default constructor
  • 若一个class没有任何constructor,而其内含的member object有default constructor,编译器则需要为此class合成default constructor
//B内含Aclass A {    public:    A();    A(int);    ...}class B{    public:    B a;    char* str;}//由于A有default constructor,而B内含A且B没有任何constructor,因此编译器需要为B合成一个default constructor,让其来调用A的default constructor处理B::a//此处B中被合成的default constructor样子inlineB::B(){    a.A::A();}
  • 若class B中内含有一个及以上的member class objects,那么class B中的每一个constructor必须调用每一个member class的default constructor;如果是多个,编译器安插代码,会按照member objects在class中声明顺序来调用对应的constructor。因此,编译器会扩张已存在的constructor,在其中安插必要代码,使其先调用member class的default constructor

    //若我们为B写一个default constructor让其初始化strB::B() { str = 0; };//此时会出现一个问题,那就是B中已经存在一个我们写的default constructor,编译器不能为其再合成一个,那么我们便没法调用a的constructor。//此时,编译器会扩张已存在的constructor,在其中安插必要代码,使其先调用member class的default constructor//扩张后的B的default constructor内部结构B::B(){    a.A::A();    str = 0;}

含有default constructor 的 base class

  • 一个没有任何constructor的class派生自含有default constructor的base class,编译器会调用上层的default constructor,为derived class合成default constructor;若base class含有许多constructors,但没有default constructor,编译器会扩张每一个constructor,安插必要代码,以此调用必要的default constructor。因为存在其他constructors,并不会合成default constructor

含有virtual functions的 class

class A{    public:    virtual void do() = 0;}void do( const A& a ) { a.do(); };void act(){    //B和C派生自A    B b;    C c;        do( b );    do( c );}
  • 此时编译器会产生两个扩张行为:

    • virtual function table被编译器产出,其中存放virtual functions地址
    • 每一个class object中,vptr会被编译器合成,内含与之相关的vtbl地址
  • 并且,a.do()的virtual invocation会被改写

    //原本a.do()( *a.vptr[1] )( &a )
    • 1表示do()在vtbl中的固定索引
    • &a表示交给被调用的do()实例的this指针
  • 为了让以上机制发挥效果,编译器必须为base和其derived class object的vpty设定初值,指向相关的virtual table地址。这样的任务会交给constructor做扩张

含有virtual base class的class

  • 由于在编译期时,virtual base class在每一个derived class object内存布局中位置不能够确定,因此编译器需要合成default constructor,在derived class object中的每一个virtual base classes中安插一个指针,以此确定其位置

copy constructordefault memberwise initialization

  • 以下三种情况会以一个object的内容作为另一个class object的初值:

    • 显示地以一个object内容作为另一个class object的初值

    • object被当做参数传给某函数

    • 函数传回class object

      //第一种class A {...};A a;A aa = a;//第二种void do1( A a );void do2(){    A aa;        foo(aa);}//第三种A do3(){    A a;    return a;}
  • 若一个class object没有explict copy constructor,而当class object以相同的class的另一个object作为初值时,此class object内部会把每一个内部或derived的data member的值,从另一个object拷贝一份到自己这,再以递归的方式进行memberwise initialization,但它并不拷贝member class object

  • memberwise initialization这一机制由bitwise copy semantics和default copy constructor实现,当class不展现bitwise copy semantics,编译器才会合成default copy constructor

  • 位拷贝(浅拷贝/bitwise copy semantics):编译器只是直接将data member的值或指针的值拷贝过来,并不拷贝member class object;也就是说这会导致多个指针指向同一对象

不展现bitwise copy semantics

  • 以下四种情况不会展现bitwise copy semantics:
    • class内含member object,而后者的class声明了copy constructor
    • class继承自base class,而base class声明了copy constructor
    • class声明一个及以上virtual functions
    • class派生自一个继承串链,其中含有一个及以上virtual base classes

对于第一、第二种情况

  • 若class展现了bitwise copy semantics且该class并没有explict copy constructor,此时编译器不会合成copy constructor

    //以下这种情况,因为展现出了bitwise copy semantics,因此编译器并不会合成copy constructorclass A{    public:    ...//不包含explict copy constructor    A(const char*);    ~A();            private:    int val;    char* str;}A a("example")void do(){    A aa = a;}//以下这种情况,因为biewise copy semantics无法调用内部class object的copy constructor,因此编译器需要合成一个copy constructor来调用class A{    public:    A(const String&);    ~A();        private:    int val;    String str;}class String{    public:    String(const char*);    String(const String&);    ~String();}

对于第三、第四种情况

  • 对于声明了virtual functions的class,编译器都会在编译器为其进行扩张,分别生成一个vptr和一个vtbl。此时用一个新的class类型object赋值给前一个class,bitwise copy semantics不在起作用,不然编译器设定的vptr并不是正确的,此时编译器需要合成default copy constructor。然而,对于一样的class object,bitwise copy semantics依然生效(除开pointer member)

    //以下这种情况bitwise copy semantics依然生效,两个class AA实例的vptr将指向同一vtblclass A{public:    A();    ~A();    virtual void do1();private:    //需要的变量}class AA : public A{public:    AA();    void do1() override;     virtual do2();private:    //需要的变量}void test(){    AA aa;    AA aa1 = aa;}//以下这种情况,两个类型的class object的vptr将指向各自相关的vtblA a = aa;
  • 一个class object如果以另一个含有virtual base class subobject作为初值,bitwise copy semantics会失效,因为virtual base class subobject的位置不确定,bitwise copy semantics会破坏这个位置,维护这个位置由编译器完成,因此需要由编译器合成default copyconstructor做出应对行为

program transformation semanticsexplict initialization

​有这样一段代码:

class X{...};//定义了copy constructorX x;void do(){    X x1(x);    X x2 = x;    X x3 = X(x);}
  • 上面的初始化操作将进行两个必要的可能的程序转化:

    • 重写每一个定义(占用内存),初始化操作剥除
    • 安插class的copy constructor
    //转化后//可能的转换,不同编译器进行的事儿不同void do(){    X x1;    X x2;    X x3;        x1.X::X(x);    x2.X::X(x);    x3.X::X(x);}

argument initialization

void do( X x );X x1;do(x1);
  • 以上代码进行argument initialization,函数会让local instance x 用memberwise将x1当作初值

  • 而对于不同的编译器,这一过程有不同的实现方法:

    • 导入临时object,调用copy constructor:

      //需要更改argument,不然需要多进行一次bitwisevoid do( X& x );//编译器产生的临时objectX _tempx;_tempx.X::X(x1);do( _tempx );

return value initialization

X do(){    X xx;    ...    return xx;}
  • 对于以上函数的返回值object,采用两步将局部对象xx拷贝而来:

    • 为函数加一个一个class object 的reference的额外参数,用来放置copy construct的返回值

    • 在return前安插copy constructor调用操作,将传回的object的内容作新增参数的初值

    • 这种方法又被称为named return value(NRV)优化

      void do( X& _result ){    X xx;        xx.X::X();        _result.X::X( xx );        return;}//do()的操作调用也随之改变X x1 = do();//转为X x1;do(x1);
  • NRV优化的缺点:

    • 优化由编译器进行,因此这种优化是否完成,我们并不知道
    • 随着函数变得越来越复杂,优化会越来越难施行

是否需要copy constructor?

  • 对于没有member或base class objects带有copy constructor,以及没有virtual base class 或 virtual function的class,并不需要copy constructor,此时memberwise即可满足要求,效率即可也安全,并不会导致memory leak,也没有address aliasing。再者,对于这类class,使用memcpy()会更有效率

    class point3d{    public:    point3d( float x, float y, float z );        private:    float _x, _y, _z;}//此时class的copy constructor这样执行更有效率point3d::point3d( const point3d& rhs ){    memcpy( this, &rhs, sizeof(point3d) );};

member initialization list

  • member initialization list不是一组函数调用,编译器安照member在class中的声明顺序一一操作member initialization list,且在任何二user code前,以适当顺序在constructor中安插初始化操作

  • 对于以下四种情况,为保证程序顺利编译,必须使用member initialization list:

    • 初始化reference member
    • 初始化const member
    • 调用base class 的constructor,而它含有一组参数
    • 调用member class的constructor,而它含有一组参数
  • 以下情况编译可以通过,但效率并不高

class A(){    String _name;    int _value; public:    A()    {        _name = 0;        _value = 0;    }}//此时A constructor会产生一个临时object,再将其初始化,随后以assignment运算符将临时Object传递给_name,最后摧毁临时object//以下是扩张A::A(){    _name.String::String();        String temp = String(0);        //memberwise拷贝    _name.String::operator=(temp);        temp.String::~String();        _value = 0;}
  • 对此进行修改

    //运用member initialization listA::A : _name(0){    _cnt = 0;}//如下扩张A::A(){    _name.String::String(0);    _value = 0;}
  • 若不注意初始化顺序和member initialization list的排列顺序,可能会出现以下错误:

    class B{    int i;    int j;public:    B(int val) : j(val), i(j)    {            }};//执行顺序 i = j; j = val;
  • 以上程序改善:将一个member的初始化操作放在constructor

    B::B(int val) : j(val){    i = j;}
  • 对于member function,请不要使用member initialization list将member function作为另一个对象的初值,因为并不知道member function对class object依赖性,因此需要将其放于constructor