RAII资源请求即初始化
2014-12-27 21:57
218 查看
维基百科:RAII
RAII全称为Resource Acquisition Is Initialization,它是在一些面向对象语言中的一种惯用法。RAII源于C++,在Java,C#,D,Ada,Vala和Rust中也有应用。1984-1989年期间,比雅尼·斯特劳斯特鲁普和安德鲁·柯尼希在设计C++异常时,为解决资源管理时的异常安全性而使用了该用法[1],后来比雅尼·斯特劳斯特鲁普将其称为RAII[2]。
RAII要求,资源的有效期与持有资源的对象的生命期严格绑定,即由对象的构造函数完成资源的分配(获取),同时由析构函数完成资源的释放。在这种要求下,只要对象能正确地析构,就不会出现资源泄露问题。
RAII的主要作用是在不失代码简洁性[3]的同时,可以很好地保证代码的异常安全性。
下面的C++实例说明了如何用RAII访问文件和互斥量:
C++保证了所有栈对象在生命周期结束时会被销毁(即调用析构函数)[4],所以该代码是异常安全的。无论在write_to_file函数正常返回时,还是在途中抛出异常时,都会引发write_to_file函数的堆栈回退,而此时会自动调用lock和file对象的析构函数。
当一个函数需要通过多个局部变量来管理资源时,RAII就显得非常好用。因为只有被构造成功(构造函数没有抛出异常)的对象才会在返回时调用析构函数[4],同时析构函数的调用顺序恰好是它们构造顺序的反序[5],这样既可以保证多个资源(对象)的正确释放,又能满足多个资源之间的依赖关系。
由于RAII可以极大地简化资源管理,并有效地保证程序的正确和代码的简洁,所以通常会强烈建议在C++中使用它。
RAII在C++中的应用非常广泛,如C++标准库中的lock_guard便是用RAII方式来控制互斥量:
程序员可以非常方便地使用lock_guard,而不用担心异常安全问题
实际上,C++标准库的实现就广泛应用了RAII,典型的如容器、智能指针等。
RAII还有另外一种被称为RRID(Resource Release Is Destruction)的特殊用法[6],即在构造时没有”获取”资源,但在析构时释放资源。ScopeGuard[7]和Boost.ScopeExit就是RRID的典型应用:
ScopeGuard通常用于省去一些不必要的RAII封装,例如
在D语言中,scope关键字也是典型的RRID用法,例如
虽然RAII和finally都能保证资源管理时的异常安全,但相对来说,使用RAII的代码相对更加简洁。正如Stroustrup所说,“在真实环境中,调用资源释放代码的次数远多于资源类型的个数,所以相对于使用用finally来说,使用RAII能减少代码量。”[8]
例如在Java中使用finally来管理Socket资源
在采用RAII后,代码可以简化为
特别是当大量使用Socket时,那些重复的finally就显得没有必要。
RAII全称为Resource Acquisition Is Initialization,它是在一些面向对象语言中的一种惯用法。RAII源于C++,在Java,C#,D,Ada,Vala和Rust中也有应用。1984-1989年期间,比雅尼·斯特劳斯特鲁普和安德鲁·柯尼希在设计C++异常时,为解决资源管理时的异常安全性而使用了该用法[1],后来比雅尼·斯特劳斯特鲁普将其称为RAII[2]。
RAII要求,资源的有效期与持有资源的对象的生命期严格绑定,即由对象的构造函数完成资源的分配(获取),同时由析构函数完成资源的释放。在这种要求下,只要对象能正确地析构,就不会出现资源泄露问题。
作用[编辑]
RAII的主要作用是在不失代码简洁性[3]的同时,可以很好地保证代码的异常安全性。下面的C++实例说明了如何用RAII访问文件和互斥量:
#include <string> #include <mutex> #include <iostream> #include <fstream> #include <stdexcept> void write_to_file(const std::string & message) { // mutex to protect file access static std::mutex mutex; // lock mutex before accessing file std::lock_guard<std::mutex> lock(mutex); // try to open file std::ofstream file("example.txt"); if (!file.is_open()) throw std::runtime_error("unable to open file"); // write message to file file << message << std::endl; // file will be closed 1st when leaving scope (regardless of exception) // mutex will be unlocked 2nd (from lock destructor) when leaving // scope (regardless of exception) }
C++保证了所有栈对象在生命周期结束时会被销毁(即调用析构函数)[4],所以该代码是异常安全的。无论在write_to_file函数正常返回时,还是在途中抛出异常时,都会引发write_to_file函数的堆栈回退,而此时会自动调用lock和file对象的析构函数。
当一个函数需要通过多个局部变量来管理资源时,RAII就显得非常好用。因为只有被构造成功(构造函数没有抛出异常)的对象才会在返回时调用析构函数[4],同时析构函数的调用顺序恰好是它们构造顺序的反序[5],这样既可以保证多个资源(对象)的正确释放,又能满足多个资源之间的依赖关系。
由于RAII可以极大地简化资源管理,并有效地保证程序的正确和代码的简洁,所以通常会强烈建议在C++中使用它。
典型用法[编辑]
RAII在C++中的应用非常广泛,如C++标准库中的lock_guard便是用RAII方式来控制互斥量:template <class Mutex> class lock_guard { private: Mutex& mutex_; public: lock_guard(Mutex& mutex) : mutex_(mutex) { mutex_.lock(); } ~lock_guard() { mutex_.unlock(); } lock_guard(lock_guard const&) = delete; lock_guard& operator=(lock_guard const&) = delete; };
程序员可以非常方便地使用lock_guard,而不用担心异常安全问题
extern void unsafe_code(); // 可能抛出异常 using std::mutex; using std::lock_guard; mutex g_mutex; void access_critical_section() { lock_guard<mutex> lock(g_mutex); unsafe_code(); }
实际上,C++标准库的实现就广泛应用了RAII,典型的如容器、智能指针等。
RRID[编辑]
RAII还有另外一种被称为RRID(Resource Release Is Destruction)的特殊用法[6],即在构造时没有”获取”资源,但在析构时释放资源。ScopeGuard[7]和Boost.ScopeExit就是RRID的典型应用:#include <functional> class ScopeGuard { private: typedef std::function<void()> destructor_type; destructor_type destructor_; bool dismissed_; public: ScopeGuard(destructor_type destructor) : destructor_(destructor), dismissed_(false) {} ~ScopeGuard() { if (!dismissed_) { destructor_(); } } void dismiss() { dismissed_ = true; } ScopeGuard(ScopeGuard const&) = delete; ScopeGuard& operator=(ScopeGuard const&) = delete; };
ScopeGuard通常用于省去一些不必要的RAII封装,例如
void foo() { auto fp = fopen("/path/to/file", "w"); ScopeGuard fp_guard([&fp]() { fclose(fp); }); write_to_file(fp); // 异常安全 }
在D语言中,scope关键字也是典型的RRID用法,例如
void access_critical_section() { Mutex m = new Mutex; lock(m); scope(exit) unlock(m); unsafe_code(); // 异常安全 } Resource create() { Resource r = new Resource(); scope(failure) close(f); preprocess(r); // 抛出异常时会自动调用close(r) return r; }
对比finally[编辑]
虽然RAII和finally都能保证资源管理时的异常安全,但相对来说,使用RAII的代码相对更加简洁。正如Stroustrup所说,“在真实环境中,调用资源释放代码的次数远多于资源类型的个数,所以相对于使用用finally来说,使用RAII能减少代码量。”[8]例如在Java中使用finally来管理Socket资源
void foo() { Socket socket; try { socket = new Socket(); access(socket); } finally { socket.close(); } }
在采用RAII后,代码可以简化为
void foo() { try (Socket socket = new Socket()) { access(socket); } }
特别是当大量使用Socket时,那些重复的finally就显得没有必要。
相关文章推荐
- 资源获取即初始化【RAII】
- RAII技术--获取资源即初始化
- RAII(Resource Acquisition Is Initialization:资源获取即初始化)
- 资源获取即初始化RAII
- c++ RAII 资源管理就是初始化
- RAII(Resource Acquisition Is Initialization)资源获得式初始化
- boost学习之RAII机制(资源申请即初始化)
- C++必知必会 - RAII(资源获取及初始化)
- RAII 资源获取就是初始化
- 5.Boost之“资源申请即初始化” RAII
- RAII:资源获取即初始化(resourse acquisition is initialization)
- RAII-获取资源即初始化
- RAII:资源获取即初始化
- RAII:资源获取即初始化
- RAII(Resource Acquisition Is Initialization资源获取即初始化 )思想
- RAII,也称为“资源获取就是初始化”
- 浅谈----RAII资源获得即初始化(Resource Acquisition Is Initialization)
- C++ 资源管理 —— RAII
- nodejs ejs 请求路径和静态资源文件路径
- Servlet和SpringMVC的初始化及请求处理过程浅析