[英]Destructor not invoked when an exception is thrown in the constructor
為什么在此代碼中不調用析構函數?
#include <boost/scoped_ptr.hpp>
#include <iostream>
class MyClass {
boost::scoped_ptr<int> ptr;
public:
MyClass() : ptr(new int) { *ptr = 0; throw; std::cout<<"MyClass Allocated\n"; }
~MyClass() { std::cout<<"MyClass De-allocated\n"; }
int increment() { return ++*ptr; }
};
int main()
{
boost::scoped_ptr<MyClass> myinst(new MyClass);
std::cout << myinst->increment() << '\n';
std::cout << myinst->increment() << '\n';
}
編輯
從答案中,了解當構造函數中發生異常時,不會調用析構函數。 但是如果異常發生在main()中,即在完全實例化MyClass對象之后,是否會調用MyClass析構函數? 如果沒有,那為什么它是一個智能指針?
添加代碼
#include <boost/scoped_ptr.hpp>
#include <iostream>
class MyClass {
boost::scoped_ptr<int> ptr;
public:
MyClass() : ptr(new int) { *ptr = 0; std::cout<<"MyClass Allocated\n"; }
~MyClass() { std::cout<<"MyClass De-allocated\n"; }
int increment() { return ++*ptr; }
};
int main()
{
boost::scoped_ptr<MyClass> myinst(new MyClass);
throw 3;
std::cout << myinst->increment() << '\n';
std::cout << myinst->increment() << '\n';
}
輸出:
MyClass Allocated
terminate called after throwing an instance of 'int'
Aborted
C ++對象的生命周期僅在其構造函數成功完成后才開始。
由於在構造函數調用完成之前拋出了異常,因此沒有完整的對象,因此沒有析構函數。
Herb Sutter 很好地解釋了這一點 ,引用他的話:
問: 從構造函數中發出異常是什么意思?
答:這意味着建築已經失敗,對象從未存在過,它的生命從未開始。 實際上,報告構造失敗的唯一方法 - 即無法正確構建給定類型的功能對象 - 是拋出異常。 (是的,現在有一個過時的編程約定,“如果你遇到麻煩,只需將狀態標志設置為'壞'並讓調用者通過IsOK()函數檢查它。”我現在將對此發表評論。)
在生物學方面,
構思開始了 - 構造函數開始了 - 但盡管做了最大的努力,然后是流產 - 構造函數從未運行到期限(ination)。順便說一句, 這就是為什么如果構造函數沒有成功就永遠不會調用析構函數 - 沒有什么可以破壞的。
"It cannot die, for it never lived."
請注意,這使得短語"an object whose constructor threw an exception"
實際上是矛盾的。 這樣的事情甚至比一個前對象還要少......它從來沒有生存過,從來沒有,從未放過第一個。 這是一個非對象。我們可以總結一下C ++構造函數模型如下:
或者:
(a)構造函數通常在到達其結尾或返回語句時返回,並且該對象存在。
要么:
(b)構造函數通過發出異常退出,並且該對象不僅現在不存在,而且從不作為對象存在。
編輯1:
但是如果異常發生在main()
,即在完全實例化MyClass
對象之后,是否會調用MyClass
析構函數?
是的,它會!
這是使用scoped_ptr
的目的,一旦在main
拋出異常,Stack Unwinding將導致所有本地對象被釋放,這意味着myinst
(駐留在堆棧上)也將被釋放,這反過來將調用析構函數MyClass
。
scoped_ptr
類模板存儲指向動態分配對象的指針。 (動態分配的對象是使用C ++新表達式分配的。) 保證在刪除scoped_ptr
或通過顯式reset
刪除指向的對象
編輯2:
為什么您編輯的程序會崩潰?
您的程序顯示崩潰,因為,您拋出異常,但您從未捕獲它。 當這種情況發生時,會調用一個名為terminate()
的特殊函數,其默認行為是調用abort()
它是實現定義的行為,在此特定場景中調用terminate()
之前堆棧是否已解繞Ref 1。查看您的實現沒有你也不應該依賴這種行為。
您可以按如下方式修改程序以處理異常,您應該得到您期望的行為:
#include <boost/scoped_ptr.hpp>
#include <iostream>
class MyClass {
boost::scoped_ptr<int> ptr;
public:
MyClass() : ptr(new int) { *ptr = 0; std::cout<<"MyClass Allocated\n"; }
~MyClass() { std::cout<<"MyClass De-allocated\n"; }
int increment() { return ++*ptr; }
};
void doSomething()
{
boost::scoped_ptr<MyClass> myinst(new MyClass);
throw 3;
}
int main()
{
try
{
doSomething();
}
catch(int &obj)
{
std::cout<<"Exception Handled";
}
}
Ref 1 C ++ 03 15.5.1 terminate()函數
在以下情況下,必須放棄異常處理以獲得不那么微妙的錯誤處理技術:
....
- 當異常處理機制找不到拋出異常的處理程序時(15.3),
....在這種情況下,
- void terminate();
被稱為(18.6.3)。 在沒有找到匹配處理程序的情況下,無論堆棧是否在調用
terminate()
之前展開,它都是實現定義的。 在所有其他情況下,在調用terminate()
之前不應解開堆棧。 基於確定展開過程最終將導致調用terminate()
不允許實現過早地完成堆棧展開。
因為在這種情況下調用析構函數沒有意義。
你只破壞構造的東西,但你的對象永遠不會完全構造。 但是,你的班級成員已經建成了,他們將會召喚他們的析構函數。
當從構造函數拋出異常時(開始或中途或調用結束時),則確保不構造對象。
因此,它定義為不調用從未構造的對象的析構函數。
以下是Bjarne網站上的一個相關常見問題解答 。
從未調用MyClass
的析構函數,因為沒有構造MyClass
類型的對象。 由於異常被拋出,每次構建一次的嘗試都被中止。
順便說一句,如果你想要顯示你的調試消息 - 特別是如果你正在處理程序崩潰 - 你真的應該刷新流:即使用std::endl
而不是'\\n'
結束時線。 (或插入std::flush
)
雖然僅使用'\\n'
經常有效,但有足夠的情況會失敗,如果你沒有養成正確的習慣,調試真的很麻煩。
如果構造函數拋出異常,則不會調用該類的析構函數,因為該對象未完全構造。
請參閱此鏈接如何在這種情況下管理資源:
http://www.parashift.com/c++-faq-lite/exceptions.html#faq-17.10
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.