显式析构函数的陷阱及解决方案(转)

 
        为了理解这个问题,我们必须首先弄明白“堆”和“栈”的概念。
堆区(heap) —— 一般由程序员分配释放(如用new申请的内存), 若程序员不释放,程序结束时可能由OS回收 。注意它与数据结构中的堆是两回事,分配方式          倒是类似于链表。
栈区(stack)—— 由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。
        我们构造对象,往往都是在一段语句体中,比如函数,判断,循环,还有就直接被一对“{}”包含的语句体。这个对象在语句体中被创建,在语句体结束的时候被 销毁。问题就在于,这样的对象在生命周期中是存在于栈上的。也就是说,如何管理,是系统完成而程序员不能控制的。所以,即使我们调用了析构,在对象生命周 期结束后,系统仍然会再调用一次析构函数,将其在栈上销毁,实现真正的析构。
        所以,如果我们在析构函数中有清除堆数据的语句,调用两次意味着第二次会试图清理已经被清理过了的,根本不再存在的数据!这是件会导致运行时错误的问题,并且在编译的时候不会告诉你!
1。显式调用的时候,析构函数相当于的一个普通的成员函数
2。编译器隐式调用析构函数,如分配了对内存,显式调用析构的话引起重复释放堆内存的异常
3。把一个对象看作占用了部分栈内存,占用了部分堆内存(如果申请了的话),这样便于理解这个问题
      系统隐式调用析构函数的时候,会加入释放栈内存的动作(而堆内存则由用户手工的释放
      用户显式调用析构函数的时候,只是单纯执行析构函数内的语句,不会释放栈内存,摧毁对象

class aaa
{
public:
    aaa(){}
    ~aaa(){cout<<"deconstructor"<<endl; }
    void disp(){cout<<"disp"<<endl;}
private:
    char *p;
};
void main()
{
aaa a;
a.~aaa();
a.disp();
}
这样的话,显式两次destructor,第一次析构相当于调用一个普通的成员函数,执行函数内语句,显示
第二次析构是编译器隐式的调用,增加了释放栈内存的动作,这个类未申请堆内存,所以对象干净地摧毁了,
显式+对象摧毁;
#include <iostream>

using namespace std;
class aaa
{
public:
    aaa(){p = new int(3);}
    ~aaa()
    {
        cout<<"deconstructor"<<endl;
        delete p;//手工释放堆内存
    }
    void disp(){cout<<"disp"<<endl;}
private:
    int *p;
};
void main()
{
    aaa a;
    a.~aaa();//显示调用析构函数
    a.disp();
}




这样的话,第一次显式调用析构函数,相当于调用一个普通成员函数,执行函数语句,释放了堆内存,但是并未释放栈内存,对象还存在(但已残缺,存在不安全因素);
第二次调用析构函数,再次释放堆内存(此时报异常),然后释放栈内存,对象销毁
 
解决方案:添加一个bool变量(类成员)
#include <iostream>
using namespace std;
class aaa
{
public:
    aaa()
    {
        p = new int(3);
        heap_deleted=false;
    }
    ~aaa()
    {
        cout<<"deconstructor"<<endl;
        if(heap_deleted==false)//判断堆是否已被释放
        {
            delete p;
            heap_deleted=true;
        }
        
        
    }
    void disp(){cout<<"disp"<<endl;}
private:
    int *p;
    bool heap_deleted;//标志,以便判断堆是否被释放
};
void main()
{
    aaa a;
    a.~aaa();//显示调用析构函数
    a.disp();
    cout<<"main_end"<<endl;
}



 

posted @ 2013-08-07 16:00  burcher  阅读(943)  评论(1编辑  收藏  举报