构造函数抛异常的替代方案

华为《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();
}

对应的打印操作如下:


调用 构造
调用 析构


分离了资源分配和对象构造,发现析构函数调用成功了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值