第2章 构造函数语意学

2.1 默认构造函数的构建操作

 关于C++的默认构造函数的构建是在被需要的时候被编译器产生出来的,关于其中的"被需要的时候"指的是被编译器需要的时候,不包括被程序员需要的时候。其中被编译器需要的时候大致包括以下四种:

  1. 该类中含有类对象成员(该成员含有默认构造函数)。

  2. 该类的基类中含有默认构造函数

  3. 该类中含有虚函数

  4. 该类在继承体系中存在一个或多个的虚基类。

注: 在添加默认构造函数时,如果该类中含有其他构造函数,这些构造函数将会被扩充如:

class B{
    public:
        B(): B_data_member(0){}
    private:
        B_data_member;
}


class A{
    public:
        A(int i = 0){
            A_data_member = i;
        }
    
    private:
        
        B A_data_member;
}

在其进行初始化时会进行下面的操作
    A(){
        A_data_member.B::B();
    }
    A(int i = 0){
        A_data_member.B::B();
        A_data_member = i;
    }

当然在有基类的构造函数进行填充时,基类默认构造函数在其类成员默认构造函数之前,即:基类默认构造函数--->类成员默认构造函数--->该类被执行构造函数内部的相关成员变量的初始化。 

2.2 拷贝构造函数的构建操作

将一个对象的内容传给另一个对象,一般大约有三种情况如:赋值,传参,调用函数返回接收。

在对象的初始化过程中,如果以一个对象作为另一个对象的初值时,关于其Data Member将会从一个对象直接赋值到另一个对象中,但是member class object 将会采用递归调用的方式进行赋值,

class A{
    public:
        ...
    private:
        int cnt;
        char *str;
}

在进行赋值时会发生
    A a;
    A b = a;
则会以
    b.cnt = a.cnt;
    b.str = a.str;
的形式进行复制。

关于非Bitwise Copy Semantics(位逐次拷贝)的情况有一下四种:

1. 当一个类中含有另一个类的成员时,且其自身含有一个Copy Constructor(包括程序员自身写的或者由编译器进行合成的)。

2. 该类的基类中含有一个Copy Construcor。

3. 该类中含有Virtual Function。

4. 在继承的体系链中含有Virtual Base Class。

注:与默认构造函数的生成相同。

class A{
    public:
        A(){}
        ~A(){}
        virtual void draw();
        virtual void run();
    private:
        ...
}

class B: public A{
    public:
        B(){}
        ~B(){}
        void draw();
        void run();
    private:
        ...
}


B b;
A a = b;

在最后的赋值中,a的赋值如下图

其中a得到的时A类中的virtual table的地址,但是当采用指针或者引用的方式的时候a才将会只想B中的virtual table,如: A *a = b;

 2.3 程序转化语意学

明确的初始化操作

1. 这种初始化的过程大致分为两大类。

  1.重写每一个定义,其中的初始化操作将被删除。

  2.类的拷贝构造函数将被添加进去。

T t0;

void foo(){
    T t1(t0);
    T t2 = t0;
    T t3 = T(t0);
}


针对以上t1, t2, t3的将被转化为一下操作

void foo(){
    T t1;
    T t2;
    T t3;
    
    
    t1.T::T(t0);
    t2.T::T(t0);
    t3.T::T(t0);
}

2. 参数的初始化

void foo(T t);


T t1;
foo(t1);

该操作将被转化为
T __Temp; //临时对象
__Temp.T::T(t1);
foo(__Temp);

上述过程为临时对象通过T的拷贝构造函数进行初始化,然后通过位逐位拷贝的形式传给局部参数t。
对于该转化的说明还有一部分,但是目前不是很清晰原因。

  3. 返回值的初始化

关于返回值的初始化问题大致分为两步

  1. 首先加上一个额外参数,类型是该类型的返回类型的一个引用,用来放置被"拷贝构建"而得的返回值。

  2. 在函数的return之前安插一个拷贝构造函数调用操作,以便将欲传回的对象的内容当做上述新增参数的初值。

定义如下
T foo(){
    T t;
    // 相关操作
    return t;
}


该操作将被转化为
void foo(T& __result){
    T t;
    t.T::T();
    __result.T::T(t);
    return;
}

至于foo().member_function()的调用则会转化为
T __Temp;
(foo(__Temp), __Temp).member_function();的操作

4. 在使用者层面做的优化

定义如下
T foo(const P &p1, const P &p2){
    T t;
    // 用p1,p2进行t的相关操作
    return t;
}

在这个定义中会出现逐成员的拷贝到编译器所产生的__result之中,可以在T的内部定义一个构造函数可以直接计算t的值,如:
T foo(const P &p1, const P &p2){
    return T(p1, p2);
}
经这个转化后,比以前的效率要调出一部分。

 5. 程序在编译器方面的优化

在能够对一个类进行bitwise方式的初始化时,编译器会在类中进行调用memcpy(this, 0, sizeof(class_name)), 或者进行memset()的操作。如:

class T{
    friend T foo(double);
    public:
        T(){
            memcpy(this, 0, sizeof(T));
        }
    private:
        double array[100];
}

  如果一个类中有以下四种情况时:

1. 含有一个类成员(其含有默认的构造函数时)。

2. 其base class中含有默认的构造函数时。

3. 含有virtual function时

4. 在继承的体系中含有一个或多个的virtual class时。

仍以bitwise的方式进行初始化会发生错误。如:

class T{
    public:
        T(){
            memcpy(this, 0, sizeof(T));
        }
        
        virtual ~T();
        //...
}
其中的T::T()会被改写为:
T::T(){
    __vptr_T = __vtb1_T;
    memcpy(this, 0, sizeof(T));
}
进而导致__vptr_T被再次进行赋值为0,而丢失它的virtual table

 2.4 成员们的初始化队伍

成员初始化时必须要用到初始化列表的情况有:

1.初始化一个引用类型的对象时。

2.初始化一个const 成员时。

3.调用一个基类的构造函数,而且该构造函数具有一组参数时。

4.调用一个类成员的构造函数时,而且该后早函数具有一组参数时。

在类的初始化构造过程中也会发生一些改写。

class T{
    private:
        String _name;
        int _cnt;
    public:
        T(){
            _name = 0;
            _cnt = 0;
        }
};

则被改写为

class T{
    private:
        String _name;
        int _cnt;
    public:
        T(){
            _name.String::String();
            String tmp = String(0);
            _name.String::operator=(tmp);
            tmp.String::~String();
            _cnt = 0;
        }
};

在应用初始化列表时

class T{
    private:
        String _name;
        int _cnt;
    public:
        T(): _name(0){
            _cnt = 0;
        }
};

改写为

class T{
    private:
        String _name;
        int _cnt;
    public:
        T(){
            _name.String::String(0);
            _cnt = 0;
        }
};

  注:编译器拓展的代码会出现在程序员自己写的代码之前,如:_cnt的赋值之前。

在使用初始化列表进行初始化时注意变量在类内部的声明顺序。

class T{
    private:
        int i;
        int j;
    public:
        T(int val): j(val), i(j) //这里会警告
        {}
};

因为在初始化的过程中先初始化i被初始化为未经初始化的j,而发出警告。

class T{
    private:
        int i;
        int j;
    public:
        T(int val): i(val), j(i)
        {}
};        

  在初始化的过程中也可以调用drived class member function,其返回值可以作为基类构造函数的一个参数

class T: public B{
    private:
        int _fval;
    public:
        int fval(){return _fval}
        T(int val): _fval(val), B(fval){}
};

其中的构造函数会被改写为:
T::T(int val){
    ...
    B::B(this, this->_fval);
    _fval = val;
}

 

 

posted @ 2019-03-05 18:53  楓羽  阅读(197)  评论(0编辑  收藏  举报