构造函数与析构函数的坑

May 6, 2013


最近在某次代码中踩到了一个坑,这个坑比较隐蔽,发生问题的地方通常让人很难往那个地方想,因此我把它叫做C++中的容易踩进去的坑。

C++中,构造函数负责分配内存空间,为类成员变量初始化,析构函数则负责所有占用销毁。于是,我们理所当然的在程序退出的时候将所有需要销毁的内容都放在析构函数中。但是有一种情况可能忽略了,一旦这个构造函数或者析构函数内部发生异常了,那情况是怎样的?析构函数内部发生异常了所造成的后果可以得知,一些内容为未能及时销毁,这些未能及时销毁的内容说不定就为下次内存泄露埋下伏笔了。那么构造函数内部发生异常呢?

class A  
{  
public:  
    A(int a)  
    {  
        std::wcout << L"A::A()" << endl;   
    }  
    ~A()  
    {  
        std::wcout << L"A::~A()" << endl;  
    }  
};  
  
class B  
{  
public:  
    B(int b)  
    {  
        std::wcout << L"B::B()" << endl;  
        throw 0;  
    }  
    ~B()  
    {  
        std::wcout << L"B::~B()" << endl;  
    }  
};  
  
class C  
{  
public:  
    C():a(0),b(0)  
    {  
        //throw 0;  
    }  
    ~C()  
    {  
        std::wcout << L"C::~C()" << endl;  
    }  
  
private:  
    A a;  
    B b;  
};  
  
int _tmain(int argc, _TCHAR* argv[])  
{  
    C();  
    return 0;  
}  

上面的代码运行后,只打出了A::A() B::B(),也许有人说,a和b已经成功构造了。等等,为什么没有看到析构函数呢?我们来捕获一下。

int _tmain(int argc, _TCHAR* argv[])  
{  
    try  
    {  
        C();  
    }  
    catch(...)  
    {  
        std::wcout << L"error!" << endl;  
    }  
    return 0;  
}  

打印结果:
alt text

看!发生了什么?B的析构函数没有成功执行,C的析构函数也没有成功执行。这是为什么?将class C改写一下:

class C  
{  
public:  
    C():a(0),b(0)  
    {  
        throw 0;  
    }  
    ~C()  
    {  
        std::wcout << L"C::~C()" << endl;  
    }  
  
private:  
    A a;  
    B b;  
};  

打印结果:
alt text

打印结果和上面依旧一样。A成功构造了也成功析构了。B成功构造了也成功析构了,C成功构造了也成功析构了。等等,什么?B成功构造了?请看class B的申明,B::B()中抛出了一个异常,也就是说B并没有成功构造,这样也就导致了C也没有成功构造,由此得出两点:
1、构造函数成功执行了的,析构函数肯定会执行.
2、构造函数没有成功构造的时候,析构函数就不执行了.

这样你可能说这搞毛啊?确实是搞毛。所以我们所要注意的就是:
1、保证析构函数能够成功析构,否则会引起内存泄露,方法有二,一是在析构时如果抛出异常可以结束程序运行,二是捕获异常继续析构。还有一种方法是抑制异常,此方法不保证。(针对析构异常可以看 Effective C++ Item8)
2、当构造函数傻逼的时候,我也不知道最正确的做法是什么,反正我是把异常抛出来或者直接将程序kill掉。是选择接受失败还是选择恢复执行,就看我们自己的选择。