【深度探索C++对象模型读书笔记】【第2章】构造函数语意学
一、Default Construct的构造操作
1、 C++ Standard说:对于一个类,如果没有用户定义的构造函数,那么会有一个默认构造函数被隐式声明出来,但这个构造函数是trivial constructor(没啥用的)。
2、 以下4种情况编译器会合成nontrivial default construct:
a) 带有default constructor的member class object
如果一个class没有任何constructor,但它内含一个member object,而后者有default constructor,那么编译器需要为该class合成一个default constructor。
如果一个class内含一个或一个以上的memberclass objects,那么这个class的每一个constructor必须调用每一个member class的default constructor。编译器会扩张已存在的constructors,在其中安插一些代码,使得user code被执行之前先以“member objects在class中的声明顺序”来调用各个default constructors。
例子:
class Dopey{ public:Dopey(); ...}; class Sneezy{ public:Sneezy(int); Sneezy(); ... }; class Bashful{ public:Bashful(); ... }; class Snow_White{ public: Dopey dopey; Sneezy sneezy; Bashful bashful; // ... private: int mumble; }; 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(); // explicit user code mumble = 2048; }
b) 带有default construct的base class
如果一个没有任何constructors的class派生自一个“带有default constructor”的base class,那么这个derived class的default constructor会被视为nontrivial,并因此需要被合成出来。
c) 带有一个virtual function的class
下面两个扩张操作会在编译期间发生:
1.一个virtualfunction table会被编译器产生出来,内放class的virtual functions地址;
2.在每一个classobject中,一个额外的pointer member(也就是vptr)会被编译器合成出来,内含相关的class vtbl的地址。
d) 带有一个virtual base class的class
3、 在合成的default constructor中,只有base class subobjects和member class objects会被初始化,所有其它的nonstaticdata member(如整数、整数指针、整数数组等等)都不会被初始化。
二、Copy Construct的构造操作
1、 有三种情况,会以一个class的内容作为另一个class object的初值:
a) 对一个object作显示的初始化操作,如:X x2= x1;
b) 一个object被当作参数交给某个函数时,如:foo(x2);
c) 当函数返回一个class object时,如return x2。
2、 如果class没有提供一个explicit copy constructor,当class object以相同class的另一个object作为初值时,其内部是以所谓的default memberwise initialization手法完成的,即把每一个内建的或派生的data member的值,从一个object拷贝到另一个object。不过它并不会拷贝其中的member class object,而是以递归的方式施行memberwise initialization。
3、 default constructors和copy constructors在必要的时候才由编译器产生出来。“必要”意指当class不展现bitwisecopy semantics时。没有default constructors和copy constructors时,编译器通过bitwise copy来产生类,就是将源类中的成员变量中的每一位都逐次复制到目标类中。这样导致如果类中的成员变量有一个指针,则只拷贝了指针值,两个指针指向同一块内存。
#include<iostream> using namespace std; class Word{ public: Word(const char* s){ str = new char[strlen(s) + 1]; strcpy(str, s); cnt = strlen(s) + 1; } ~Word(){ delete[] str; } public: char* str; int cnt; }; int main(){ Word noun("book"); Word verb = noun; cout << noun.str << endl; cout << noun.cnt << endl; cout << static_cast<void*>(noun.str) << endl; //两个对象的str指针指向同一块内存 cout << static_cast<void*>(verb.str) << endl;//两个对象的str指针指向同一块内存 system("pause"); return 0; }
4、 以下4种情况,一个class不展现bitwisecopy semantics:
a) class内含一个member object而后者的class声明或被编译器合成有一个copy constructor时;
b) 当class继承自一个base class而后者存在或被编译器合成有一个copy constructor时;
c) 当class声明了一个或多个virtual functions时;
d) 当class派生自一个继承串链,其中有一个或多个virtual base classes时。
5、 编译器对于每一个新产生的class object的vptr不能成功而正确地设好其初值,将导致可怕的后果。当编译器导入一个vptr到class之中时,该class就不再展现bitwise semantics。
6、 当一个base class objec以其derived class的object内容做初始化操作时,其vptr复制操作也必须保证安全。
7、 每一个编译器对于虚拟继承的支持承诺,都代表必须让derived class object中的virtual base classsubobject的位置在执行期准备妥当。维护位置的完整性是编译器的责任。
三、程序转化语意学
1、 显示的初始化操作都有两个必要的程序转化阶段:
a) 重写每一个定义,剥除其中的初始化操作;
b) 安插class的copy constructor调用操作。
2、 把一个class object当作参数传给一个函数或是作为一个函数的返回值,相当于以下形式的初始化操作:X xx = arg; 其中xx代表形式参数或返回值,而arg代表真正的参数值。形参必须从原先的类的对象改变为类的引用,避免复制。
3、 函数定义如下:
X bar(){ X xx; //处理xx…… return xx; }
bar()的返回值通过一个双阶转化从局部对象xx中拷贝出来:
a) 首先添加一个额外参数,类型是class object的一个reference,这个参数用来放置被拷贝构建而得的返回值。
b) 在return指令之前安插一个copy constructor调用操作,以便将欲传回之object的内容当作上述新增参数的初值,同时重写函数使它不返回任何值。
bar()转换如下:
void bar(X& _result){ X xx; //编译器所产生的default constructor调用操作 xx.X:X(); //......处理xx //编译器所产生的copy constructor调用操作 _result.X::X(xx); return; }
4、 Named Return Value(NRV)优化如今被视为是标准C++编译器的一个义不容辞的优化操作,特点是直接操作新添加的额外参数。只有copy constructor的出现才会激活C++编译器的NRV优化。NRV优化虽然极大地改善了效率,但还是饱受批评:一是优化由编译器默默完成,而是否完成以及其完成程度完全透明;二是一旦函数变得比较复杂,优化就变得比较难以施行;三是优化由可能使程序产生错误——有时并不是对称地调用constructor和destructor。
四、成员们的初始化队伍
1、 必须使用member initialization list:
a) 初始化一个reference member时;
b) 初始化一个const member时;
c) 调用一个base class的constructor,而它拥有一组参数时;
d) 调用一个member class的constructor,而它拥有一组参数时。
2、 编译器会一一操作initialization list,以适当顺序在constructor内安插初始化操作,并置于任何explicit user code之前。
3、 initialization list的项目顺序是由class中的members声明顺序决定的,不是由initialization list中的排列顺序决定的。
#include<iostream> using namespace std; class X{ private: int i; int j; public: X(int val) :j(val), i(j){} void printij(){ cout << i << endl; //乱七八糟的一个数 cout << j << endl; //5 } }; int main(){ X x(5); x.printij(); system("pause"); return 0; }
版权声明:本文为博主原创文章,未经博主允许不得转载。
posted on 2015-06-10 10:01 ruan875417 阅读(132) 评论(0) 编辑 收藏 举报