构造和析构的那些事

来源:互联网 发布:易语言传奇霸业源码 编辑:程序博客网 时间:2024/05/21 19:27

对《C++对象模型》、《More Effective C++》两书中关于构造以及析构函数的讲解总结:

 一.关于default constructors.

  “default constructors......在需要的时候被编译出来” 此处是指在编译器需要的时候被编译出来,被合成出来的constructor只执行编译器所需的行动。即使有需要为class合成一个default constructor,  也不会将两个data members val 和 pnext 初始化为0.

class Foo{   public: int val;    Foo * pnext;};void foo_bar(){       Foobar;       if(bar.val|| bar.pnext)       //..do something       //..}


“对于class X, 如果没有任何 user-declared constructor, 那么会有一个 default constructor 被隐式(implicitly)声明出来......一个被隐式声明出来的constructor将是一个trival constructor(没啥用的)”

1. 如果一个class 没有任何constructor, 但它内含一个member object,而后者有default constructor,那么个这个class的implicit default constructor 就是"nontrival",编译器需要为该class 合成一个default constructor. 不过这个合成操作只有在constructor 真正需要被调用时才会发生。  C++ 语言要求以"member objects 在class 中的声明顺序" 来调用各个constructors, 以inline的方式安插在程序代码中。

2. 带有default constructor 的 base class

类似道理,如果一个没有任何constructor的class 派生自一个“带有default constructor”的 base class, 那么这个 derived class 的default constructor 会被视为nonvtrival

3. 带有一个virtual Function 的class

  class声明或继承一个virtual function

  class 派生自一个继承串链,其中有一个或多个virtual base classes     vtbl 以及 vptr

4. 带有virtual Base class 的 class

  必须使virtual base class 在其每一个 derived class object 中的位置,能够于执行期准备妥当。

 C++新手一般有两个误解:

1.  任何class如果没有定义default constructor,就会被合成出一个来。

2.  比那机器合成出来的default constructor 会显示设定"class 内每一个 data member 的默认值"

二.copy constructor 的构造操作

有三种情况,会以一个object的内容作为另一个class object的初值

1.显示的赋值初始化; 

2.object当做参数交给某个函数 ;

3.当函数返回一个class object default memberwise initialization;

 把每一个内建的或派生的data member(例如一个指针或一个数组)的值,从某个object 拷贝一份到另一个object上。不过它并不会拷贝其中member class object,而是以递归的方式施行memberwise initialization。

 一个class 可用两种方式复制得到,一种是被初始化(copy constructor),另一种是被指定(copy assignment operator)。

 决定一个copy constructor是否为trival 的标准在于class 是否展现出所谓的 bitwise copy semantics。

三. member initialization list

在下列情况下,为了让你的程序能够被顺利编译,你必须使用member initialization list:

1. 初始化一个reference member;

2. 初始化一个 const member;

3. 调用一个base class 的 constructor, 而它拥有一组参数;

4. 调用一个member class的constructor, 而它拥有一组参数时。

list中的项目顺序是由class中的members 声明顺序决定的,不是由initialization list中的排列顺序决定的,

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


i(j)其实比j(val)更早执行,因为j一开始没有值,所以i(j)执行结果就是i无法预知。

正确的写法    initialization list 的项目被放在explicit user code 之前。

X(int val): j(val) {i = j;}

简单的说,编译器会对initializationlist 一一处理并可能重新排序,members 的声明顺序。它会被安插一些代码到constructor体内,并置于任何explicit user code 之前。

四. 析构语意学

如果class没有定义destructor, 那么只有在class 内含memberobject(抑或class 自己的base class)拥有destructor 的情况下,编译器才会自动合成出一个来。甚至虽然它拥有一个virtualfunction,都不会。

1. destructor 的函数本体首先被执行;

2.如果class 拥有member class object,而后者拥有destructor,那么它们会以其声明顺序的相反顺序被调用;

3.如果object 内含一个vptr,现在被重新设定,指向适当之base class 的virtual table。

 

继承体系下的对象构造

constructor 可能内含大量的隐蔽码,因为编译器会扩充每一个constructor,扩充程度视class T 的继承体系而定。一般而言,编译器所做的扩充操作如下:

1. member initialization list 中的data members 初始化操作会被放进 constructor 的函数本体,并以members 的声明顺序为顺序;

2,如果一个member并没有出现在memberinitialization list 中,但它有一个default constructor, 那么该default constructor 必须被调用;

3. 在那之前,如果class object 有virtual table pointers, 它们必须被设定为初值,指向适当的 virtual tables;

4.在那之前,所有上一层的base class constructors 必须被调用,以base class 的声明顺序为顺序(与memberinitialization list 中的顺序没有关联);

5.所有的virtual base class constructors 必须被调用,从左到右,从最深到最浅。

虚拟继承

传统的“constructor扩充现象”并没有发生,这是因为virtual base class 的共享性的缘故。

五. 构造函数以及析构函数中内存泄露的问题

void processAdoptions(istream&dataSource){       while(dataSource)       {             ALA *pa =readALA(dataSource);             pa->processAdoption();              deletepa;       }} 


每当readALA 被调用,便产生一个新的heap object。如果没有调用delete,这个循环很快便会出现资源泄露的问题。

如果pa->processAdoption抛出一个exception,processAdoption 便会发生一次资源泄露。

简单的解决方法:

 voidprocessAdoptions(istream& dataSource){       while(dataSource) {              ALA *pa =readALA(dataSource);              try{                     pa->processAdoption();                  }       }              catch(....) {                     deletepa;           throw;              }              delete pa;       //如果没有exception 被抛出,也要避免资源泄露       }} 


代码凌乱, 思考如何把delete动作从process Adoptions函数移动到函数内某个局部对象的destructor内。

解决的办法就是,以一个"类似指针的对象"取代指针pa。如此当这个类似指针的对象被自动销毁的时候,我们可以令其destructor调用delete。 行为类似 smart pointers。

(我们必须明智和审慎的使用智能指针,因为至少需要加上 copy constructor, assignment operator 以及指针仿真函数 operator* 和 operator->)....

以auto_ptr 对象取代原始指针,就不需要担心heap objects没有被删除,即使在exception 被抛出的情况下。

void processAdoptions(istream&dataSource){       while(dataSource)       {              auto_ptr<ALA>pa(readALA(dataSource));  //pa被声明为auto_ptr<ALA>对象,自动析构              pa->processAdoption();            //循环后不再有delete 语句       }}


一个对象存放"必须自动释放的资源",并依赖该对象的destructor 释放

void displayInfo(const Information&info){       WINDOW_HANDLE  w(createWindow());       displayinfo in wondow corresponding to w;       destroyWindow(w);}class WindowHandle{public: WindowHandle(WINDOW_HANDLE handle):w(handle) {}       ~WindowHandle(){destroyWindow(w);}       operatorWINDOW_HANDLE() {return w;}private:   WINDOW_HANDLE W;       WindowHandle(constWindowHandle&);       WindowHandle&operator=(const WindowHandle);}void displayInfo(const Information&info){       WondowHandlew(createWindow());       displayinfo in window vorresponding to w;   //坚持把资源封装在对象内,通常可以避免资源泄露}在constructor 内阻止资源泄露(resource leak):BookEntry::BookEntry(const string&name,                               const string& address,                                   conststring& imageFileName,                                   conststring& audioClipFilename):theName(name),theAddress(address),theImage(0), theAudioClip(0){       if(imageFile!= " ") {           theImage = new Image(imageFileName);        }       if(audioClipFileName!= ""){       theAudioClip = new AutdioClip(audioClipFileName);    }}BookEntry:: ~BookEntry(){       delete the Image;     //C++保证删除null指针是安全的       deletetheAudioClip;}


可是当 newAudioClip(audiocliopFileName) 出现异常,控制权移出BookEntry constructor, 此时BookEntry 的 destructor 不会调用来删除theImage已经指向的对象。 C++只会析构已构造完成的对象。

0 0
原创粉丝点击