华为《C++编码规范》 规则7.1 构造和析构函数不能抛出异常
说明:如果构造和析构函数执行失败则无法安全地撤销和回滚,故这些函数不能向外抛出异常。为了降低复杂性,建议在这类函数中实现最简单的逻辑。
下面将分两方面介绍Init()函数如何替代构造函数处理异常:
->为何构造函数不能抛异常。
->为何使用Init函数,可以抛异常
一、为什么构造函数要避免抛异常?
究其原因,是受构造函数特殊性和异常处理机制决定的。
首先,要了解异常的处理流程,:https://blog.csdn.net/Innocent_code/article/details/85012145,已了解忽略
我们看一下构造函数发生异常的代码:
#include <QCoreApplication>
#include <stdio.h>
#include <conio.h>
class ExceptionMaker{
public:
//析构函数
~ExceptionMaker()
{
printf("调用 析构\n");
if(pBuff != nullptr)
{
delete pBuff;
}
if(p != nullptr)
{
delete p;
}
}
//构造函数
ExceptionMaker()
{
printf("调用 构造\n");
pBuff = new char[100];
//这里申请内存过大,会失败抛异常
p = new char[0xffffffff];
}
private:
void *pBuff; //申请动态内存的指针
void *p;
};
//产生异常和捕获异常函数
void ExceptionFun()
{
ExceptionMaker *e = nullptr;
//try块与异常捕获代码
try
{
e = new ExceptionMaker();
}
catch(std::bad_alloc)
{
printf("bad alloc exception!\n");
}
//若e为nullptr,析构函数将不会被调用,这一句是安全代码
delete e;
}
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
ExceptionFun();//产生异常
return a.exec();
}
上面的代码打印如下:
调用 构造
bad alloc exception!
析构函数居然没有被调用,OOH,My god,异常导致了栈解退,保存pBuff的指针在森林里丢失了,pBuff指向的堆内存怎么办!!!答案只有一个:那段内存已经泄露。
构造函数的特殊性:构造函数(比如类ExceptionMaker)失败,会析构掉已经构造的成员,如果已经构造的成员是一个结构体,那么该成员的析构函数被调用(虽然,代码中对象存在包含关系,但是在栈中,它们将被一视同仁,顺序排列,基类和子类亦是如此。于是,成员或基类对象的栈解退将独立完成)。但是ExceptionMaker对象没有构造完成,所以,ExceptionMaker对象的析构成为不安全操作,所以析构函数不会被调用。于是导致了指针p的内存泄漏问题。
二、为何使用Init函数,可以抛异常
分析到这里,那么怎么解决构造函数抛异常,所导致的内存泄漏问题?
答案是:将对象构造与内存申请分开。即:提供单独的Init函数或者使用工厂模式。两种方法虽然说法不同,但解决问题的方式是相同的。
接下来,我们一起看下面的代码:
#include <QCoreApplication>
#include <stdio.h>
#include <conio.h>
class ExceptionMaker{
public:
//析构函数
~ExceptionMaker()
{
printf("调用 析构\n");
if(pBuff != nullptr)
{
delete pBuff;
}
if(p != nullptr)
{
delete p;
}
}
//构造函数
ExceptionMaker()
{
printf("调用 构造\n");
p = nullptr;
pBuff = nullptr;
}
//负责分配资源的初始化函数
void Init()
{
pBuff = new char[100];
//这里申请内存过大,会失败抛异常
p = new char[0xffffffff];
}
private:
void *pBuff; //申请动态内存的指针
void *p;
};
//产生异常和捕获异常函数
void ExceptionFun()
{
ExceptionMaker *e = nullptr;
//try块与异常捕获代码
try
{
e = new ExceptionMaker();
e->Init();
}
catch(std::bad_alloc)
{
printf("bad alloc exception!\n");
}
//若e为nullptr,析构函数将不会被调用,这一句是安全代码
delete e;
}
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
ExceptionFun();//产生异常
return a.exec();
}
对应的打印操作如下:
调用 构造
调用 析构
分离了资源分配和对象构造,发现析构函数调用成功了。