• 文章
  • 如何在 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 的引用... 我们只需要弹出它并销毁它... 因此没有内存泄漏的机会...