• 文章
  • 如何在 C++ 構造中處理異常
2011 年 11 月 16 日 (最後更新: 2011 年 11 月 30 日)

如何使用 Boost 的 Shared Ptr 在 C++ 建構函式中處理異常

得分: 3.2/5 (138 票)
*****
在 C++ 中,如果一個類的建構函式丟擲異常(例如記憶體分配異常),我們應該如何處理,這是一個非常常見的問題。 考慮以下程式碼片段。 這裡 A 類的建構函式丟擲了一個異常。所以處理這種情況的最佳方法是在一個 try 塊中例項化 A。如果在 A 的建構函式中丟擲異常,i 將被堆疊展開銷燬,並且丟擲的異常將被捕獲...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class MyException(string str){
private: 
string  msg;
public:
MyException(string str){
msg = str;
}
void printerrmsg(){
cout<<msg.c_str()<<endl;
}
}

class A{
private: int i;

//if exception is thrown in the constructor of A, i will de destroyed by stack unwinding
//and the thrown exception will be caught
A()
{
i = 10;
throw MyException(“Exception thrown in constructor of A()”);
}
};
void main(){
try{
A();
}
catch(MyException& e){
e.printerrmsg();
}
}

現在還有另外一個問題... 假設我們需要在建構函式中分配一些動態記憶體... 在執行此操作時,建構函式丟擲一個記憶體異常... 因此,引用基於堆的記憶體的基於堆疊的指標將被銷燬,因為堆疊展開... 所以我們將有一些沒有被任何指標引用的記憶體,因此我們無法訪問它... 所以很明顯這是一個記憶體洩漏... 那麼,我們如何處理這個問題...

在現代 C++ 中處理這種情況的最佳方法是使用 auto_ptr/shared_ptr... 所以解決方案看起來像下面這樣

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
#include <iostream>
#include <string>
#include <memory>
#include <boost/shared_ptr.hpp>
#include <boost/shared_array.hpp>

using namespace std;

class SomeClass{
public:
 SomeClass(){}
 ~SomeClass(){};
};

typedef boost::shared_ptr<SomeClass> pSomeClass;
typedef boost::shared_ptr<char> pChar;
typedef boost::shard_array<char> charBuff;

class MyException{
public:
 MyException(string str){
 msg = str;
 }
 void printerrmsg(){
  cout<<msg.c_str()<<endl;
 }
private:
 string msg;
};
class A{
private:
 int i;
 pChar m_ptrChar;
 pSomeClass m_ptrSomeClass;
 charBuff m_pcharBuff;

 //if exception is thrown in the constructor of A, i will be destroyed by stack unwinding
 //and the thrown exception will be caught
public:
 A():m_ptrChar(new char),m_ptrSomeClass(new SomeClass),m_pcharBuff(new char[100])
 {
 i = 10;
 throw MyException("Exception at A's constructor");
 }
};

int main(){
 try{
 A objA;
 }
 catch(MyException& e){
  e.printerrmsg();
 }
 return 1;
 }

在 Symbian C++ 中,它透過一個叫做兩階段建構函式的概念來處理...(它之所以出現,是因為早期的 Symbian C++ 中沒有模板概念,因此沒有 auto_ptr)... 在這個過程中,如果我們想在堆上建立一個動態記憶體分配,並用 *pMem 指向它,那麼在構造的第一階段,我們將 *pMem 初始化為 NULL。 顯然這不會丟擲異常... 然後我們將這個 pMem 推入清理堆疊(這是 Symbian C++ 的一個新概念)... 並且在構造的第二階段,我們分配 pMem 指向的記憶體... 因此,如果建構函式失敗,我們仍然在清理堆疊中保留了 pMem 的引用... 我們只需要彈出它並銷燬它... 因此沒有記憶體洩漏的機會...