簡體   English   中英

在構造函數中拋出異常時,不會調用析構函數

[英]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

如有疑問,請參閱Boost文檔

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),
....

在這種情況下,

  1. 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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM