幾乎在大部分時候,我們是不需要顯式的調用析構函數的。顯式的調用析構函數是一件非常危險的事情,因為如果系統會調用析構函數,無論我們自己是否已經調用過,仍然會再次調用。換句話說,我們自己所謂的顯式調用析構函數,實際上只是調用了一個成員函數,并沒有真正意義上的讓對象“析構”。
為了理解這個問題,我們必須首先弄明白“堆”和“棧”的概念。
堆區(heap) —— 一般由程序員分配釋放, 若程序員不釋放,程序結束時可能由OS回收 。注意它與數據結構中的堆是兩回事,分配方式倒是類似于鏈表。
棧區(stack)—— 由編譯器自動分配釋放 ,存放函數的參數值,局部變量的值等。其操作方式類似于數據結構中的棧。
我們構造對象,往往都是在一段語句體中,比如函數,判斷,循環,還有就直接被一對“{}”包含的語句體。這個對象在語句體中被創建,在語句體結束的時候被銷毀。問題就在于,這樣的對象在生命周期中是存在于棧上的。也就是說,如何管理,是系統完成而程序員不能控制的。所以,即使我們調用了析構,在對象生命周期結束后,系統仍然會再調用一次析構函數,將其在棧上銷毀,實現真正的析構。
所以,如果我們在析構函數中有清除堆數據的語句,調用兩次意味著第二次會試圖清理已經被清理過了的,根本不再存在的數據!這是件會導致運行時錯誤的問題,并且在編譯的時候不會告訴你!
在網上找到這幾句話,說得很好啊:
//顯式調用的時候,析構函數相當于的一個普通的成員函數
//編譯器隱式調用析構函數,如分配了對內存,顯式調用析構的話引起重復釋放堆內存的異常
//把一個對象看作占用了部分棧內存,占用了部分堆內存(如果申請了的話),這樣便于理解這個問題
//系統隱式調用析構函數的時候,會加入釋放棧內存的動作(而堆內存則由用戶手工的釋放)
//用戶顯式調用析構函數的時候,只是單純執行析構函數內的語句,不會釋放棧內存,摧毀對象
系統在什么情況下不會自動調用析構函數呢?顯然,如果對象被建立在堆上,系統就不會自動調用。一個常見的例子是new...delete組合。但是好在調用delete的時候,析構函數還是被自動調用了。很罕見的例外在于使用布局new的時候,在delete設置的緩存之前,需要顯式調用的析構函數,這實在是很少見的情況。
所以,結論是,一般不要自作聰明的去調用析構函數。或者要是你不嫌麻煩的話,析構之前最好先看看堆上的數據是不是已經被釋放過了。放出一個演示的例子,大家可以參考一下哈。
#ifndef A_HPP
#define A_HPP
#include <iostream>
using namespace std;
class A
{
private:
int a;
int* temp;
bool heap_deleted;
public:
A(int _a);
A(const A& _a);
~A();
void change(int x);
void show() const;
};
#endif
#include "a.hpp"
A::A(int _a): heap_deleted(false)
{
temp = new int;
*temp = _a;
a = *temp;
cout<< "A Constructor!" << endl;
}
A::A(const A& _a): heap_deleted(false)
{
temp = new int;
*temp = _a.a;
a = *temp;
cout << "A Copy Constructor" << endl;
}
A::~A()
{
if ( heap_deleted == false){
cout << "temp at: " << temp << endl;
delete temp;
heap_deleted = true;
cout << "Heap Deleted!\n";
}
else {
cout << "Heap already Deleted!\n";
}
cout << "A Destroyed!" << endl;
}
void A::change(int x)
{
a = x;
}
void A::show() const
{
cout << "a = " << a << endl;
}
#include "a.hpp"
int main(int argc, char* argv[])
{
A a(1);
a.~A();
a.show();
cout << "main() end\n";
a.change(2);
a.show();
return 0;
}
posted on 2008-04-12 14:29
lf426 閱讀(2497)
評論(1) 編輯 收藏 引用 所屬分類:
語言基礎、數據結構與算法