首页 > 解决方案 > 类的构造函数中的异常会导致内存泄漏吗?

问题描述

由于异常后未调用 B 构造函数,因此存在内存泄漏。如果该构造函数分配其他类的对象并且在某个时候发生异常,则在构造函数中处理异常的最佳方法是什么,它需要释放所有先前分配的对象,或者有办法自动管理它。

#include <iostream>
#include<utility>
using namespace std;

class A
{
private:
    int *eleA;
public:
    A()
    {
        eleA = new int;
        //*eleA = 0;
        cout<<"Constructor A\n";
    }
    ~A()
    {
        cout<<"Destructor A \n";
        delete eleA;
        eleA = nullptr;
    }
};
class B
{
private:
    int *eleB;

public:
    B()
    {
        cout<<"Constructor B\n";
        eleB = new int;
        //*eleB = 0;
        throw -1; //Exception thrown
    }
    ~B()
    {
        cout<<"Destructor B \n";
        delete eleB;
        eleB = nullptr;
    }
};

class AB
{
private:
    A *a_ptr;
    B *b_ptr;
public:

    AB() : a_ptr(nullptr),b_ptr(nullptr)
    {
      try{
        cout<<"Constructor AB \n";
        a_ptr = new A;
        b_ptr = new B;
      }
      catch(int a)
      {
          cout<<"Exception catched\n"; 
      }
    }
    ~AB()
    {
        cout<<"Destructor AB \n";
        if (a_ptr)
        {
            delete a_ptr;
            a_ptr = nullptr;
        }
        if (b_ptr)
        {
            delete b_ptr;
            b_ptr = nullptr;
        }
    }
};
int main()
{
    AB *ab = new AB;

    delete ab;

    return 0;
}

输出:

构造函数 AB

构造函数 A

构造函数 B

捕捉到异常

析构函数 AB

析构函数 A

标签: c++oopc++11

解决方案


显而易见的答案是“使用智能指针”。替换int *eleA;std::unique_ptr<int> eleA;,替换eleA = new int;eleA = std::make_unique<int>();,删除delete eleA;,你就完成了(对你拥有的其他指针重复同样的事情)。此类对象将被自动删除,因此您不必担心泄漏。

但是,了解可用于管理任意资源(不仅是指针)的通用解决方案仍然很好,例如来自 C API 的各种句柄。该解决方案是范围警卫

它们不在标准 C++ 中,但实现它们很容易,因此提供它们的库数不胜数。您可以自己编写它们;示例实现在答案的末尾

以下是您如何使用它们:

int main()
{
    int *ptr = new int;
    FINALLY( delete ptr; )
    // Do something with `ptr` here.
}

FINALLY()是一个创建范围保护的宏。这个想法delete ptr;不是立即执行,而是在控制离开当前范围时执行,即到达右括号},或者由于异常。这意味着您不再需要担心删除ptr,无论是否有任何异常。

我们如何将其应用于您的构造函数?我们制作了一个不同的宏,仅当您因异常而离开范围时才运行代码:

B()
{
    cout<<"Constructor B\n";
    eleB = new int;
    FINALLY_ON_THROW( delete eleB; )
    *eleB = 0;
    throw -1; //Exception thrown
}

这可以推广到任意数量的资源。您只需在创建它们中的每一个后放置这样的范围保护。


范围保护实现示例:

#include <exception>
#include <utility>

namespace Macro
{
    template <typename T> class ScopeGuard
    {
        T func;
      public:
        ScopeGuard(T &&func) : func(std::move(func)) {}
        ScopeGuard(const ScopeGuard &) = delete;
        ScopeGuard &operator=(const ScopeGuard &) = delete;
        ~ScopeGuard() {func();}
    };

    // Same as `ScopeGuard`, but can throw.
    template <typename T> class ScopeGuardExc
    {
        T func;
      public:
        ScopeGuardExc(T &&func) : func(std::move(func)) {}
        ScopeGuardExc(const ScopeGuardExc &) = delete;
        ScopeGuardExc &operator=(const ScopeGuardExc &) = delete;
        ~ScopeGuardExc() noexcept(false) {func();}
    };
}

#define FINALLY_impl_cat(a, b) FINALLY_impl_cat_(a, b)
#define FINALLY_impl_cat_(a, b) a##b

#define FINALLY(...) \
    ::Macro::ScopeGuard FINALLY_impl_cat(_finally_object_,__LINE__) \
    ([&]() -> void { __VA_ARGS__ });

#define FINALLY_ON_THROW(...) \
    ::Macro::ScopeGuard FINALLY_impl_cat(_finally_object_,__LINE__) \
    ([&, _finally_exc_depth_ = ::std::uncaught_exceptions()]() -> void { if (::std::uncaught_exceptions() > _finally_exc_depth_) {__VA_ARGS__} });

#define FINALLY_ON_SUCCESS(...) \
    ::Macro::ScopeGuardExc FINALLY_impl_cat(_finally_object_,__LINE__) \
    ([&, _finally_exc_depth_ = ::std::uncaught_exceptions()]() -> void { if (::std::uncaught_exceptions() <= _finally_exc_depth_) {__VA_ARGS__} });

推荐阅读