充分使用RAII

一、C++ 资源管理

写代码时经常会碰到这种情况:在函数开始时申请了buffer、或打开了某文件、打开某so,需要在函数返回前做对应的释放或关闭操作。不使用任何语言机制的情况下,写出来的代码是这样的:

void before() {
    uint8_t *a = new uint8_t[10];
    double *b = new double(1.2);
    if (xxx) {
        delete[] a;
        delete b;
        return;
    }
    if (xxx) {
        delete[] a;
        delete b;
        return;
    }
    delete[] a;
    delete b;
}

在任意一个函数出口,都要进行资源释放,代码丑陋、可读性差,而且还容易出现某代码块抛异常直接退出但资源没有释放的情况。

在C++中已经内置了管理内存的RAII类,即智能指针,直接用即可。需要注意两点:

  • 如果资源不需要被共享,出函数之前就需要释放,则优先用unique_ptr
  • 如果是申请数组,unique_ptr提供了对数组的特化,但shared_ptr直到c++17才支持对数组的特化。没有条件用较新编译器的时候需要自己写deleter
    • c++11/14的写法1:shared_ptr<int> a(new int[10], default_delete<int[]>());
    • c++11/14的写法2:shared_ptr<int> a(new int[10], [](int *p) {delete[] p;});
    • c++17的写法:shared_ptr<int[]> a(new int[10]);
void after() {
    unique_ptr<uint8_t[]> a(new uint8_t[10]);
    unique_ptr<double> b(new double(1.2));
    if (xxx) {
        return;
    }
    if (xxx) {
        return;
    }
}

二、利用栈变量的析构函数

除了资源管理外,如果需要在函数返回前执行某个操作,也可以使用RAII,将对应的操作放到栈变量的析构函数中去,由编译器自动为我们做这件事。

举例,需要在函数结束前将函数执行的结果错误码通过一个notify函数传递出去:

void before() {
    int ret = 0;
    if (xxx) {
        ret = -1;
        notify(ret);
        return;
    }
    if (xxx) {
        ret = -2;
        notify(ret);
        return;
    }
    notify(ret);
}

改造后:

void after1() {
    int ret = 0;

    class RAIIHelper {
    public:
        explicit RAIIHelper(int &r) : mRet(r) {}
        ~RAIIHelper() {
            notify(mRet);
        }
    private:
        int &mRet;  //传引用
    } helper(ret);

    if (xxx) {
        ret = -1;
        return;
    }
    if (xxx) {
        ret = -2;
        return;
    }
}

但是这样写可扩展性不好,这个RAIIHelper类只能在当前函数中用,在其他情况下不一定适用。

go语言中有一个特性叫defer——延迟执行,虽然C++中没有这玩意,但是我们可以用lambda表达式和function造一个出来。很简单,把需要执行的操作直接传进来,在析构函数中执行。

class Defer {
public:
    explicit Defer(function<void()> f) : m_func(move(f)) {}
    ~Defer() { m_func(); }
private:
    function<void()> m_func;
};

再次改造后,十分清爽:

void after2() {
    int ret = 0;
    Defer defer([&ret]() { notify(ret); });  //引用捕获
    if (xxx) {
        ret = -1;
        return;
    }
    if (xxx) {
        ret = -2;
        return;
    }
}

 

三、goto

C语言举例:

void before() {
    uint8_t *a = malloc(10);
    uint8_t *b = malloc(5);
    if (xxx) {
        free(a);
        free(b);
        return;
    }
    if (xxx) {
        free(a);
        free(b);
        return;
    }
    free(a);
    free(b);
}

在C中由于没有类这个概念,没法在析构函数中写入一些操作,可以用goto方式来搞:

void after1() {
    uint8_t *a = malloc(10);
    uint8_t *b = malloc(5);
    if (xxx) {
        goto End;
    }
    if (xxx) {
        goto End;
    }

End:
    free(a);
    free(b);
}

四、do while(0)大法

相对goto来说我更喜欢用do while(0)大法。在每个出口处加上break,然后在最后执行一次必要的资源释放等操作:

void after2() {
    uint8_t *a = malloc(10);
    uint8_t *b = malloc(5);
    do {
        if (xxx) {
            break;
        }
        if (xxx) {
            break;
        }
    } while(0);
    free(a);
    free(b);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值