1. 程式人生 > 其它 >C++丟擲異常後仍能呼叫作用域上文區域性變數的析構方法

C++丟擲異常後仍能呼叫作用域上文區域性變數的析構方法

技術標籤:C++

先貼現象:

#include <iostream>
#include <string>
using namespace std;
class MyExcept
{
public:
	explicit MyExcept(string err) : errmsg(err) {}
	string getErrMsg() { return errmsg; }
private:
	string errmsg;
};

class Demo{
public:
    string field;
	Demo (string val):field(val) {}
	~Demo(){
		cout << "Will destructor be called for "+field << endl;
	}
};

int main(int argc, char* argv[]){
    Demo d("papa"),d2("auau");
    // throw MyExcept("Throw exception in member function");
	try{
		Demo d("heihei"),d2("haha");
        throw MyExcept("Throw exception in member function");
        /*
            丟擲異常後將逆序呼叫try域內區域性變數的解構函式
            ,然後才進入catch。前提是該異常要被try...catch捕獲,
            如果沒有捕獲異常就不能析構try域以及try域外上文作用域的區域性變數
        */
	}catch (MyExcept& e){
		cout << e.getErrMsg() << endl;
	}
	return 0;
}

輸出:

VMCatalina:Exception haypin$ ./main
Will destructor be called for haha
Will destructor be called for heihei
Throw exception in member function
Will destructor be called for auau
Will destructor be called for papa
VMCatalina:Exception haypin$

不捕獲異常:


int main(int argc, char* argv[]){
    Demo d("papa"),d2("auau");
    throw MyExcept("Throw exception in member function");
	return 0;
}

輸出:

VMCatalina:Exception haypin$ clang++ -g except.cpp -o main
VMCatalina:Exception haypin$ ./main
libc++abi.dylib: terminating with uncaught exception of type MyExcept
Abort trap: 6
VMCatalina:Exception haypin$ 

可以看到:只要捕獲了異常,則丟擲異常位置的上文作用域(包括try域以及外層的上文作用域)的區域性變數將被逆序析構。如果不捕獲異常就不會析構,從而造成記憶體洩漏

https://stackoverflow.com/questions/8311457/are-destructors-called-after-a-throw-in-c

的翻譯:

https://segmentfault.com/q/1010000002498987

Q51:C++在丟擲一個異常後是否會呼叫解構函式?

我運行了一個樣例並且棧分配的物件的解構函式確實被呼叫了,但這能被標準保證麼?

A71:是的,這是被保證的(前提是異常要被捕獲)。

C++11 15.2 Constructors and destructors 構造器和析構器[except.ctor]

1 As control passes from a throw-expression to a handler, destructors are invoked for all automatic objects constructed since the try block was entered. The automatic objects are destroyed in the reverse order of the completion of their construction.

1、只要將throw表示式的控制傳遞到一個處理(這指的是捕獲throw吧),那麼從進入try塊的所有自動構造的物件的解構函式都將被呼叫。這些自動物件按照它們構造完成的順序逆序被銷燬

進一步,如果在物件構造期間丟擲了異常,則部分構造的物件的子物件也能保證被正確析構

2 An object of any storage duration whose initialization or destruction is terminated by an exception will have destructors executed for all of its fully constructed subobjects (excluding the variant members of a union-like class), that is, for subobjects for which the principal constructor (12.6.2) has completed execution and the destructor has not yet begun execution. Similarly, if the non-delegating constructor for an object has completed execution and a delegating constructor for that object exits with an exception, the object’s destructor will be invoked. If the object was allocated in a new-expression, the matching deallocation function (3.7.4.2, 5.3.4, 12.5), if any, is called to free the storage occupied by the object.

2、由於一個異常導致一個物件在其儲存期間(storage duration,指生成)的初始化或構造被終止,那將為它的已經完全構造過的子物件呼叫解構函式,除了那些類似聯合類的變體成員欄位。也就是那些主構造器已經完成執行但析構器還沒有開始執行的子物件。

相似地,如果一個物件的非代表性構造器已經完成執行但其代表性的構造器由於異常而退出,那麼該物件的析構器將被呼叫。如果物件在一個new表示式中被分配,則匹配的解除分配函式(如果有的話),將被呼叫來釋放物件佔用的儲存空間。

示例:在建構函式中已經完成部分子物件的構造後丟擲異常:

#include <iostream>
#include <string>
using namespace std;
class MyExcept
{
public:
	explicit MyExcept(string err) : errmsg(err) {}
	string getErrMsg() { return errmsg; }
private:
	string errmsg;
};

class Demo{
public:
    string field;
	Demo (string val):field(val) {}
	~Demo(){
		cout << "Will destructor be called for "+field<< endl;
	}
};
class C2{
public:
    Demo field;
    C2(string val):field(val){
        throw MyExcept("Throw exception in member function");
    };
};
int main(int argc, char* argv[]){
	try{
		C2 c("heihei");//,d2("haha");
        /*
            部分初始化的物件在其建構函式中丟擲異常,將析構其子物件
        */
	}catch (MyExcept& e){
		cout << e.getErrMsg() << endl;
	}
	return 0;
}

輸出:

VMCatalina:Exception haypin$ clang++ -g except.cpp -o main
VMCatalina:Exception haypin$ ./main
Will destructor be called for heihei
Throw exception in member function
VMCatalina:Exception haypin$

動態申請記憶體:


int main(int argc, char* argv[]){
	try{
        C2 *pc=new C2("papa");
        /*
            部分初始化的物件在其建構函式中丟擲異常,將析構其子物件
        */
	}catch (MyExcept& e){
		cout << e.getErrMsg() << endl;
	}
	return 0;
}

輸出:

VMCatalina:Exception haypin$ clang++ -g except.cpp -o main
VMCatalina:Exception haypin$ ./main
Will destructor be called for papa
Throw exception in member function
VMCatalina:Exception haypin$

1和2整個過程被稱為"棧解旋"

3、對從一個try塊開始到一個throw表示式之間的路徑上自動構造的物件呼叫析構器的處理被稱為"stack unwinding"棧解旋。如果在棧解旋期間呼叫了一個構造器並且由於一個異常而退出,將呼叫std::terminate(15.5.1)。

。。。