c++ - 类的构造函数中的异常会导致内存泄漏吗?
问题描述
由于异常后未调用 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
解决方案
显而易见的答案是“使用智能指针”。替换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__} });
推荐阅读
- angular - 邪恶的 CORS/CORB - Spring 和休眠、Springboot、Maven 和 Angular js 2,不起作用 - 允许访问控制来源
- static - 在类中分配静态变量时,每次我们初始化新类时都会运行该过程吗?
- google-apps-script - 查找 NamedRange 的用法
- python - Pixel RNN Pytorch 实现
- python - 如何使用 matplotlib 在视频上绘制矩形框?
- ruby-on-rails - rails simple-form 将数据属性添加到输入选择包装器以及选择选项
- javascript - JavaScript程序在给定字符串中查找字符的出现
- sql-server - 不同服务器上的 SQL Server/Access Connection 持续维护
- docker - Docker Swarm 中的多个 Traefik 实例
- php - 使用 PHP 从 Binary SQL SERVER 下载 PDF