• C++中的RAII介绍


    摘要

    RAII技术被认为是C++中管理资源的最佳方法,进一步引申,使用RAII技术也可以实现安全、简洁的状态管理,编写出优雅的异常安全的代码。

    资源管理

    RAII是C++的发明者Bjarne Stroustrup提出的概念,RAII全称是“Resource Acquisition is Initialization”,直译过来是“资源获取即初始化”,也就是说在构造函数中申请分配资源,在析构函数中释放资源。因为C++的语言机制保证了,当一个对象创建的时候,自动调用构造函数,当对象超出作用域的时候会自动调用析构函数。所以,在RAII的指导下,我们应该使用类来管理资源,将资源和对象的生命周期绑定。

    智能指针(std::shared_ptr和std::unique_ptr)即RAII最具代表的实现,使用智能指针,可以实现自动的内存管理,再也不需要担心忘记delete造成的内存泄漏。毫不夸张的来讲,有了智能指针,代码中几乎不需要再出现delete了。

    内存只是资源的一种,在这里我们讨论一下更加广义的资源管理。比如说文件的打开与关闭、windows中句柄的获取与释放等等。按照常规的RAII技术需要写一堆管理它们的类,有的时候显得比较麻烦。但是如果手动释放,通常还要考虑各种异常处理,比如说:

    void function()
    {
        FILE *f = fopen("test.txt", 'r');
        if (.....)
        {
            fclose(f);
            return;
        }
        else if(.....)
        {
            fclose(f);
            return;
        }
    
        fclose(f);
        ......
    }

    这里介绍一个网上实现的我认为比较简洁的方法,文章在这里。作者使用了C++11标准中的lambda表达式和std::function相结合的方法,非常简洁、明了。直接看代码吧:

    #define SCOPEGUARD_LINENAME_CAT(name, line) name##line
    #define SCOPEGUARD_LINENAME(name, line) SCOPEGUARD_LINENAME_CAT(name, line)
    #define ON_SCOPE_EXIT(callback) ScopeGuard SCOPEGUARD_LINENAME(EXIT, __LINE__)(callback)
    
    class ScopeGuard
    {
    public:
        explicit ScopeGuard(std::function<void()> f) : 
            handle_exit_scope_(f){};
    
        ~ScopeGuard(){ handle_exit_scope_(); }
    private:
        std::function<void()> handle_exit_scope_;
    };
    
    int main()
    {
        {
            A *a = new A();
            ON_SCOPE_EXIT([&] {delete a; });
            ......
        }
    
        {
            std::ofstream f("test.txt");
            ON_SCOPE_EXIT([&] {f.close(); });
            ......
        }
    
        system("pause");
        return 0;
    }

    作者为了使用方便,还定义了根据行号来对ScopeGuard类型对象命名的宏定义。看到了吧,当ScopeGuard对象超出作用域,ScopeGuard的析构函数中会调用handle_exit_scope_函数,也就是lambda表达式中的内容,所以在lamabda表达式中填上资源释放的代码即可,多么简洁、明了。既不需要为每种资源管理单独写对应的管理类,也不需要考虑手动释放出现各种异常情况下的处理,同时资源的申请和释放放在一起去写,永远不会忘记。

    状态管理

    RAII另一个引申的应用是可以实现安全的状态管理。一个典型的应用就是在线程同步中,使用std::unique_lock或者std::lock_guard对互斥量std:: mutex进行状态管理。通常我们不会写出如下的代码:

    std::mutex mutex_;
    void function()
    {
        mutex_.lock();
        ......
        ......
        mutex_.unlock();
    }

    因为,在互斥量lock和unlock之间的代码很可能会出现异常,或者有return语句,这样的话,互斥量就不会正确的unlock,会导致线程的死锁。所以正确的方式是使用std::unique_lock或者std::lock_guard对互斥量进行状态管理:

    std::mutex mutex_;
    void function()
    {
        std::lock_guard<std::mutex> lock(mutex_);
        ......
        ......
    }

    在创建std::lock_guard对象的时候,会对std::mutex对象进行lock,当std::lock_guard对象在超出作用域时,会自动std::mutex对象进行解锁,这样的话,就不用担心代码异常造成的线程死锁。

    总结

    通过上面的分析可以看出,RAII的核心思想是将资源或者状态与对象的生命周期绑定,通过C++的语言机制,实现资源和状态的安全管理。理解和使用RAII能使软件设计更清晰,代码更健壮。

    参考

    1、http://mindhacks.cn/2012/08/27/modern-cpp-practices/

    2、http://www.jellythink.com/archives/101

    3、http://www.cppblog.com/aaxron/archive/2011/03/22/142475.html

  • 相关阅读:
    javascript 3秒钟后自动跳转到前一页面
    meta
    HTML 5 label
    WCF的ABC
    由于 Web 服务器上的“ISAPI 和 CGI 限制”列表设置,无法提供您请求的页面。
    ECMASCRIPT5新特性(转载)
    bin目录正.pdb是什么文件?
    PS切图的相关技巧
    MongoVUE破解方法
    ASP.NET MVC Area操作
  • 原文地址:https://www.cnblogs.com/jiangbin/p/6986511.html
Copyright © 2020-2023  润新知