您的位置:首页 > 编程语言 > C语言/C++

【深度探索C++对象模型读书笔记】【第2章】构造函数语意学

2015-06-10 10:01 381 查看
一、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;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: