如果構(gòu)造函數(shù)內(nèi)發(fā)生異常,已經(jīng)分配的資源是不會(huì)自動(dòng)釋放的,比如
class B{
public:
B(){
printf("into B constructor\n");
}
~B(){
printf("into B destructor\n");
}
};
class C{
public:
C(){
printf("into C constructor\n");
throw std::runtime_error(" exception from C constructor");
}
~C(){
printf("into C destructor\n");
}
};
class A{
public:
A(){
printf("into A constructor \n");
}
~A(){
printf("into A destructor \n");
}
};
class D:A{
public:
D():A(), b(NULL),c(NULL) {
printf("into D constructor\n");
b = new B();
c = new C();
}
~D(){
printf("into D destructor\n");
delete b;
delete c;
}
private:
B *b;
C *c;
};
int main(int argc, char **argv){
D d;
return 0;
}
運(yùn)行結(jié)果如下:
into A constructor
into D constructor
into B constructor
into C constructor
terminate called after throwing an instance of 'std::runtime_error'
what(): exception from C constructor
對(duì)象c在構(gòu)造過程中拋出異常,指針b指向的內(nèi)存空間不會(huì)被釋放。
如何釋放b的內(nèi)存呢?首先我們可以看出c++是不會(huì)調(diào)用析構(gòu)函數(shù)的,因?yàn)槲鰳?gòu)函數(shù)不知道對(duì)象已經(jīng)構(gòu)造了多少,哪些資源該釋放,哪些不該釋放,當(dāng)然編譯器可以記錄這些內(nèi)容,但是會(huì)嚴(yán)重影響效率。另外在語義上,c++認(rèn)為,對(duì)象的生命周期是構(gòu)造函數(shù)正常結(jié)束至析構(gòu)函數(shù)結(jié)束之間,構(gòu)造不完全的對(duì)象是一個(gè)沒有生命的東西,是不存在的,你不能對(duì)一個(gè)不存在的對(duì)象調(diào)用析構(gòu)函數(shù)。編譯器默認(rèn)會(huì)做的只是釋放對(duì)象d的內(nèi)存空間。對(duì)象b指向的堆內(nèi)存可以通過使用異常顯示釋放
D():A(), b(NULL), c(NULL){
printf("into D constructor\n");
try{
b = new B();
c = new C();
}catch(std::runtime_error &e){
printf("into D constructor catch \n");
delete b; b=NULL;
delete c; c=NULL;
}
運(yùn)行結(jié)果如下:
into A constructor
into D constructor
into B constructor
into C constructor
into D constructor catch
into B destructor
into D destructor
into A destructor
b被正常釋放了,我們?cè)僮鲆幌赂淖儯瑢和c的構(gòu)造放到初始化列表中做,將D的構(gòu)造函數(shù)改成下面這樣,
D::D() : A(),b(new B()),c(new C())
{
}
我們繼續(xù)使用異常,會(huì)不會(huì)有效?
D() try:A(), b(new B()), c(new C()) {
printf("into D constructor\n");
}catch(std::runtime_error &e){
printf("in D constructor catch: %s \n", e.what());
cleanup();
}
看上去very nice啊,跑一下試試,
into A constructor
into B constructor
into C constructor
into A destructor
in D constructor catch: exception from C constructor
into B destructor
into C destructor
*** glibc detected *** ./a.out: free(): invalid pointer: ***
指針錯(cuò)誤!同時(shí)我們可以發(fā)現(xiàn)在進(jìn)入catch語句前基類A執(zhí)行了析構(gòu)函數(shù),這說明到達(dá)catch語句時(shí),已經(jīng)跳出了構(gòu)造函數(shù)的范圍,D和A的成員數(shù)據(jù)都已經(jīng)是不可訪問的了。
C++為什么要這樣做呢,為什么構(gòu)造函數(shù)體外的catch語句中無法訪問數(shù)據(jù)成員?
首先,無法知道b和c是否已經(jīng)初始化了,刪除未初始化指針是非法的。
其次,我們假設(shè)可以知道b初始化了,c沒有初始化,我們要?jiǎng)h除b,如果catch中可以訪問b和c的話(我們做個(gè)假設(shè)),改變B的類型,使B包含一個(gè)A*成員數(shù)據(jù),考慮下這種情況,
D() try:A(), b(new B(static_cast(this))), c(new C()) {
printf("into D constructor\n");
}catch(std::runtime_error &e){
printf("in D constructor catch: %s \n", e.what());
cleanup();
}
A已經(jīng)析構(gòu)了,b的數(shù)據(jù)成員A已經(jīng)不存在,這是很危險(xiǎn)的。
c++認(rèn)為,不管是基類還是數(shù)據(jù)成員構(gòu)造出現(xiàn)失敗,那么整個(gè)對(duì)象構(gòu)造都是失敗的,不存在半成品。構(gòu)造函數(shù)塊外的catch語句(上面這種)即使沒有顯示地重新拋出異常,c++也會(huì)自動(dòng)拋出,一直到最下層派生類對(duì)象構(gòu)造處停住。比如以上例子,catch沒有顯示throw,在main函數(shù)里:
try{
D d;
}catch (std::runtime_error &e){
printf("int main: %s\n", e.what());
}
仍然會(huì)捕捉到C構(gòu)造函數(shù)拋出的runtime_error異常。
綜上,可以總結(jié)以下規(guī)則
1. 構(gòu)造函數(shù)體外的try-catch語句只有一個(gè)用途——轉(zhuǎn)換捕捉到的異常對(duì)象
2. 必須在構(gòu)造函數(shù)體內(nèi)分配資源,不要在初始化列表中
3. 一定要用try-catch語句管理資源的話,在構(gòu)造函數(shù)體內(nèi)。
4. 使用RAII方式管理資源,這會(huì)少死亡很多腦細(xì)胞,像這樣子
class D{
auto_ptr b;
auto_ptr
}
D::D() : A( ),b(new B()), c(new C())
{
}