![](/img/trans.png)
[英]Throwing exception in derived class constructor. Why is base class destructor called but not derived class destructor?
[英]Exception in derived class constructor
我在處理派生類中的構造函數異常時遇到一些問題。 當派生類構造函數引發錯誤,但父類已分配了一些對象時。 父類的析構函數會被調用嗎?
例:
class A
{
A() { /* Allocate some stuff */ };
virtual ~A() { /* Deallocate stuff */ };
};
class B : public A
{
B()
{
/* Do some allocations */
...
/* Something bad happened here */
if (somethingBadHappened)
{
/* Deallocate B Stuff */
...
/* Throws the error */
throw Error; /* Will A destructor be called? I know B destructor won't */
};
};
~B() { /* Deallocate B Stuff */ };
}
我想知道執行以下操作是否是一個好主意:
B()
{
/* Do some allocations */
...
/* Something bad happened here */
if (somethingBadHappened)
{
/* Deallocate B Stuff */
this->~B();
/* Throws the error */
throw Error; /* Will A destructor be called? I know B destructor won't */
};
};
如果沒有,那么做這些事情的一種體面的方法是什么?
異常將導致堆棧展開到正確捕獲異常的位置。 這意味着在拋出異常之前在范圍內創建的任何對象都將被破壞,包括本示例中的基類對象。
嘗試這個:
#include <iostream>
class A
{
public:
A() { std::cout << "A::A()\n";}
~A() {std::cout << "A::~A()\n";}
};
class B : public A
{
public:
B()
{
std::cout << "B::B()\n";
throw 'c';
}
// note: a popular point of confusion --
// in this example, this object's destructor
// WILL NOT BE CALLED!
~B()
{
std::cout << "B::~B()\n";
}
};
int main()
{
try
{
B b;
}
catch(...)
{
std::cout << "Fin\n";
}
return 0;
}
輸出應為:(注意未調用B::~B()
)
A::A()
B::B()
A::~A()
Fin
只要您不試圖釋放尚未分配的資源,就可以按照問題中的說明手動調用析構函數。 最好將這些資源包裝在某種類型的RAII
容器中( std::auto_ptr
, boost::shared_ptr
等),以避免調用析構函數的必要性。
Mooing Duck很好地說明了在構造函數中引發異常時堆棧展開的工作方式:
還沒有完全考慮到這一點,但是可以考慮在try / catch塊中創建對象。 如果構造函數引發異常,請delete
該對象(如果該對象是使用new
創建的)。
try
{
B* b = new B();
}
catch
{
delete b;
//log error
}
如果不使用new
為b
分配內存,則無需在catch
塊中調用delete。
確保您的B
析構函數不會對從未創建的對象調用delete
。 我建議在進行可能導致異常的任何操作之前,在構造函數中設置所有指向等於0的對象的指針的成員。 這樣,如果調用了析構函數,則delete
它們是安全的。
您在問題第二部分中嘗試編寫干凈的構造函數B::B()
失敗嘗試凸顯了在一個類中承擔過多責任的設計的尷尬。 如果僅使用單一職責組件,則通常可以完全不編寫任何顯式錯誤檢查,而讓異常處理機制以遞歸方式工作。
考慮一下:
B::B()
{
try { this->p1 = get_dangerous_pointer(); }
catch(...) { throw; } // OK
try { this->p2 = suicidal_function(); }
catch(...) {
clean_up(p1);
throw;
}
try { this->p3 = get_monstrous_amounts_of_memory(); }
catch(...)
{
clean_up(p2);
clean_up(p1);
throw;
}
}
如您所見,為只承擔三種不同職責的類編寫正確的構造函數是一場噩夢。
正確的解決方案是使每個資源都歸包裝類所有,而包裝類的唯一責任是擁有該資源,即使面對最特殊的異常,清理也會自動進行。
還要注意,從任何構造函數中調用成員函數時都必須格外小心 。 對象的生命周期直到構造函數完成后才開始,因此當您在構造函數中時,您正在使用“正在構造的對象”-有點像對您自己進行的心臟直視手術。 特別是,您不得調用析構函數,因為只允許破壞完整的對象。
最好的主意是在構造中捕獲異常,然后將對象置於會產生錯誤的狀態(例如,對象讀取文件,在構造函數中打開文件失敗,然后讀取將不起作用)。
只要保持對象一致即可。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.