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; }