【问题标题】:Does Exception in Constructor of class can lead to memory leak?类的构造函数中的异常会导致内存泄漏吗?
【发布时间】:2021-07-07 18:58:20
【问题描述】:

异常后未调用 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

【问题讨论】:

  • 使用智能指针不用担心
  • 这是一个最好的例子,为什么你应该使用智能指针
  • eleA = 0; 并没有像你想象的那样做。
  • 感谢更正
  • 由于new和它的使用是一个非常高级的话题,我建议你使用智能指针和容器,只有在掌握它们之后才考虑是否使用new。 (除非您正在制作自己的自定义智能指针或棘手的容器,否则您可能永远不需要使用new。)

标签: c++ oop c++11


【解决方案1】:

显而易见的答案是“使用智能指针”。将int *eleA; 替换为std::unique_ptr&lt;int&gt; eleA;,将eleA = new int; 替换为eleA = std::make_unique&lt;int&gt;();,删除delete eleA;,您就完成了(对您拥有的其他指针重复相同的操作)。这样的对象会被自动删除,不用担心泄露。

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

它们不在标准 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__} });

【讨论】:

    猜你喜欢
    • 2020-06-06
    • 2019-08-19
    • 1970-01-01
    • 2018-11-18
    • 1970-01-01
    • 2021-03-23
    • 2021-09-25
    • 2014-12-08
    • 1970-01-01
    相关资源
    最近更新 更多