栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > C/C++/C#

充分使用RAII

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

充分使用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
void after() {
    unique_ptr a(new uint8_t[10]);
    unique_ptr 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 f) : m_func(move(f)) {}
    ~Defer() { m_func(); }
private:
    function 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);
}

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/511413.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号