c++(3)

来源:互联网 发布:wan微型端口 编辑:程序博客网 时间:2024/06/05 10:21

什么函数都有可能失败,构造函数也不另外,比如new一个对象或空间不成功。当构造函数失败的时候,其实很多时候我们不想这个对象被继续生成,这个时候就可以在构造函数里面抛出异常。C++规定构造函数抛出异常之后,对象将不被创建,析构函数也不会被执行,但已经创建成功的部分(比如一个类成员变量)会被部分逆序析构,不会产生内存泄漏。但有些资源需要在抛出异常前自己清理掉,比如打开成功的一个文件,最好关闭掉再抛出异常(虽然系统也会把这个资源回收),因为抛出异常之后析构函数不会被执行了。

      网上比较经典的总结

       (1) C++中通知对象构造失败的唯一方法那就是在构造函数中抛出异常;(这句话并不是说我们只有这个方法才能让上层知道构造函数失败,虽然构造函数没有返回值,我们完全可以在构造函数中传入一个引用值,然后在里面设置状态,运行完构造函数之后任然可以知道是否失败,但这种情况下面对象其实还是被构造出来的,只是里面有资源分配失败而已,并且析构函数还是会执行。这和我们构造失败不生成对象的初衷不符。)
  (2) 构造函数中抛出异常将导致对象的析构函数不被执行;(但已经生产的部分成员变量还是会被逆向析构的)
  (3) 当对象发生部分构造时,已经构造完毕的子对象将会逆序地被析构;


网上的一个例子

一个实例对象的构造
第一步,分配足够的内存,如果失败就是栈溢出或抛出std::bad_alloc的异常,所以在这步你不用担心内存泄露,而且这一步你是不能插手的,如果这步成功,就进入第二步。

new运算符的实现保证了内存泄漏不会发生。例如

T *p = new T;

将被编译器转换给类似下面的样子:(其实和我们自己释放已经申请的资源的思想流程是一样的)

[cpp] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. // 第一步,分配原始内存,若失败则抛出bad_alloc异常  
  2. try {  
  3.     // 第二步,调用构造函数构造对象  
  4.     new (p)T;       // placement new: 只调用T的构造函数  
  5. }  
  6. catch(...) {  
  7.     delete []p;     // 释放第一步分配的内存  
  8.     throw;          // 重抛异常,通知应用程序  
  9. }  


第二步,调用构造函数,在通常情况下,如果构造函数为空或没有进行动态内存分配,你就不用关心内存泄露了
你需要关心的是构造函数中有动态内存分配
[cpp] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. class A  
  2. {  
  3.   char* str[10];  
  4. public:  
  5.   A(){  
  6.     for(int i=0;i<10;i++)  
  7.        str[i]=NULL;     //对str[]初始化,这是必须的,不然再后面delete就会出现问题  
  8.     try{  
  9.       for(int i=0;i<10;i++)  
  10.       str[i]=new char[1024*1024*1024];   //要来就来狠的  
  11.     }  
  12.     catch(bad_alloc){  
  13.        for(int i=0;i<10;i++)  
  14.            delete []str[i];  //放心,即使delete NULL是不会出问题的  
  15.        throw;  //就抛出这个bad_alloc, 这才是构造函数抛出去的异常,外层会扑捉到,并且析构函数不会被调用  
  16.     }  
  17.   }  
  18.   ~A()  
  19.   {  
  20.     for(int i=0;i<10;i++)  
  21.      delete []str[i];           
  22.   }   
  23. };  
  24. int main()  
  25. {  
  26.     A *pA=NULL;  
  27.     try{  
  28.       pA=new A;  
  29.     }  
  30.     catch(bad_alloc){  
  31.        cout<<"Out of memory"<<endl;  
  32.     }  
  33.     delete pA;  
  34.     return 0;  
  35. }  

pA是用NULL初始化的,即使在给A分配内存时(第一步)失败,这样就不会导致后面的delete pA出错。


对于构造函数可能失败的做法一般有两种

1. 在构造函数中抛出异常,本对象构造未完成,它的析构函数不会被调用。当然,我们有义务释放已经分配到的资源。简单,最常见。
2. 把资源的初始化工作放在另一个单独函数中,比如 bool init(...),由对象创建者(比如工厂方法)先调用构造函数,再调用init方法。ATL中常见。


0 0
原创粉丝点击