当在构造函数中抛出异常的时候,由于对象尚未构造完全,因此并不会调用其析构函数,问题是此时如果对象已经被部分构造,那么我们应当保证被部分构造的内容适当地析构。
考虑这样一个类
class A{
public:
A(const int& number,const string&a,const string& b):num(number),ptr_a(0),ptr_b(0){
if (a != "")
ptr_a = new string(a);
if (b != "")
ptr_b = new string(b);
}
~A(){ delete ptr_a; delete ptr_b; }
private:
int num;
string* ptr_a;
string* ptr_b;
};
如果A的构造函数中在为ptr_b所指向的对象分配内存的时候抛出一个异常,编译器不会调用A的析构函数,那么释放ptr_a所指向的内存的任务就落到了程序员身上,可以将构造函数改写为以下形式来适当的释放ptr_a所指向的内存:
class A{
public:
A(const int& number,const string&a,const string& b):num(number),ptr_a(0),ptr_b(0){
try{
if (a != "")
ptr_a = new string(a);
if (b != "")
ptr_b = new string(b);
}
catch(...){
delete ptr_a; //可以直接delete,因为delete一个空指针不会有影响
delete ptr_b; //同上
throw;
}
}
~A(){ delete ptr_a; delete ptr_b; }
private:
int num;
string* ptr_a;
string* ptr_b;
};
不需要担心A类的non-pointer data members(指的是num),data member会在A的构造函数内代码执行之前就被初始化好(因为使用了初始化列表),一旦A类型对象被销毁,这些data member会像已经被构造好的对象一样被自动销毁。
你可以发现catch语句块内的动作与A类的析构函数动作相同, 因此可以把他们放入一个辅助函数内,如下:
class A{
public:
A(const int& number,const string&a,const string& b):num(number),ptr_a(0),ptr_b(0){
try{
if (a != "")
ptr_a = new string(a);
if (b != "")
ptr_b = new string(b);
}
catch(...){
cleanup();
throw;
}
}
~A(){ cleanup(); }
void cleanup(){
delete ptr_a;
delete ptr_b;
}
private:
int num;
string* ptr_a;
string* ptr_b;
};
以上代码可以比较好的解决构造函数内抛出异常时对象的析构问题,但是如果A类的ptr_a和ptr_b被声明为const,则需要另外一番考虑(此时必须在初始化列表内初始化ptr_a和ptr_b),A的构造函数可以像以下这样定义:
class A{
public:
A(const int& number,const string&a,const string& b)
:num(number),
ptr_a(iniA()),
ptr_b(iniB()){
}
~A(){ cleanup(); }
void cleanup(){
delete ptr_a;
delete ptr_b;
}
private:
string* iniA();
string* iniB();
int num;
string* const ptr_a;
string* const ptr_b;
};
string* A::iniA()
{
return new string("A");
}
string* A::iniB()
{
try{
return new string("B");
}
catch(...){
delete ptr_a;
}
}
我们可以继续优化,将ptr_a和ptr_b所指对象视为资源,交给局部对象来管理。
class A{
public:
A(const int& number, const string&a, const string& b) :num(number), ptr_a(a != "" ? new string(a) : 0), ptr_b(b != "" ? new string(b) : 0){
}
private:
int num;
const auto_ptr ptr_a;
const auto_ptr ptr_b;
};
//在此设计中,如果ptr_b在构造过程中有任何异常,由于ptr_a已经是构造好的对象,在运行其析构函数时,所指内存会被自动释放,此外,由于ptr_a和ptr_b如今都是对象,当当其”宿主“(A类对象)被销毁时,它们说所指内存也会被自动释放
总结: “C++ 只会析构已完成的对象”,“面对未完成的对象,C++ 拒绝调用其析构函数”,因为对于一个尚未构造完成的对象,构造函数不知道对象已经被构造到何种程度,也就无法析构。当然,并非不能采取某种机制使对象的数据成员附带某种指示,“指示constructor进行到何种程度,那么destructor就可以检查这些数据并(或许能够)理解应该如何应对。但这种机制无疑会降低constructor的效率,处于效率与程序行为的取舍,C++ 并没有使用这种机制。所以说,”C++ 不自动清理那些’构造期间跑出exception‘的对象“。



