C++程序的设计机制3 RAII机制

开发 后端
为了管理内存等资源,C++程序员通常采用RAII机制(资源获取即初始化),在使用资源的类的构造函数中申请资源,然后使用,最后在析构函数中释放资源。今天本文为你介绍RAII机制,一起来看。

RAII(Resource Acquisition Is Initialization )机制是Bjarne Stroustrup首先提出的。要解决的是这样一个问题:

在C++中,如果在这个程序段结束时需要完成一些资源释放工作,那么正常情况下自然是没有什么问题,但是当一个异常抛出时,释放资源的语句就不会被执行。于是Bjarne Stroustrup就想到确保能运行资源释放代码的地方就是在这个程序段(栈帧)中放置的对象的析构函数了,因为stack winding会保证它们的析构函数都会被执行。将初始化和资源释放都移动到一个包装类中的好处:

  • 保证了资源的正常释放
  • 省去了在异常处理中冗长而重复甚至有些还不一定执行到的清理逻辑,进而确保了代码的异常安全。
  • 简化代码体积。

1、应用场景

1)文件操作

我们可以是用这个机制将文件操作包装起来完成一个异常安全的文件类。实现上,注意将复制构造函数和赋值符私有化,这个是通过一个私有继承类完成的,因为这两个操作在此并没有意义,当然这并不是RAII所要求的。

  1. /*  
  2. * =====================================================================================  
  3. *  
  4. * Filename: file.cpp  
  5. *  
  6. * Description: RAII for files  
  7. *  
  8. * Version: 1.0  
  9. * Created: 05/09/2011 06:57:43 PM  
  10. * Revision: none  
  11. * Compiler: g++  
  12. *  
  13. * Author: gnuhpc (http://blog.csdn.net/gnuhpc), warmbupt@gmail.com  
  14. *  
  15. * =====================================================================================  
  16. */  
  17. #include   
  18. #include   
  19. #include   
  20.  
  21. using namespace std;  
  22. class NonCopyable  
  23. {  
  24. public:  
  25. NonCopyable(){};  
  26. private:  
  27. NonCopyable (NonCopyable const &); // private copy constructor  
  28. NonCopyable & operator = (NonCopyable const &); // private assignment operator  
  29. };  
  30.  
  31. class SafeFile:NonCopyable{  
  32. public:  
  33. SafeFile(const char* filename):fileHandler(fopen(filename,"w+"))  
  34. {  
  35. if( fileHandler == NULL )  
  36. {  
  37. throw runtime_error("Open Error!");  
  38. }  
  39. }  
  40. ~SafeFile()  
  41. {  
  42. fclose(fileHandler);  
  43. }  
  44.  
  45. void write(const char* str)  
  46. {  
  47. if( fputs(str,fileHandler)==EOF )  
  48. {  
  49. throw runtime_error("Write Error!");  
  50. }  
  51. }  
  52.  
  53. void write(const char* buffer, size_t num)  
  54. {  
  55. if( num!=0 && fwrite(buffer,num,1,fileHandler)==0 )  
  56. {  
  57. throw runtime_error("Write Error!");  
  58. }  
  59. }  
  60. private:  
  61. FILE *fileHandler;  
  62. SafeFile(const SafeFile&);  
  63. SafeFile &operator =(const SafeFile&);  
  64. };  
  65.  
  66. int main(int argc, char *argv[])  
  67. {  
  68. SafeFile testVar("foo.test");  
  69. testVar.write("Hello RAII");  

 

C++的结构决定了其原生支持RAII,而在Java 中,对象何时销毁是未知的,所以在Java 中可以使用try-finally做相关处理。

#p#

2)智能指针模拟

一个更复杂一点的例子是模拟智能指针,抽象出来的RAII类中实现了一个操作符*,直接返回存入的指针:

现在我们有一个类:

 

  1. class Example {  
  2. SomeResource* p_;  
  3. SomeResource* p2_;  
  4. public:  
  5. Example() :  
  6. p_(new SomeResource()),  
  7. p2_(new SomeResource()) {  
  8. std::cout << "Creating Example, allocating SomeResource!\n";  
  9. }  
  10. Example(const Example& other) :  
  11. p_(new SomeResource(*other.p_)),  
  12. p2_(new SomeResource(*other.p2_)) {}  
  13. Example& operator=(const Example& other) {  
  14. // Self assignment?  
  15. if (this==&other)  
  16. return *this;  
  17. *p_=*other.p_;  
  18. *p2_=*other.p2_;  
  19. return *this;  
  20. }  
  21. ~Example() {  
  22. std::cout << "Deleting Example, freeing SomeResource!\n";  
  23. delete p_;  
  24. delete p2_;  
  25. }  
  26. }; 

假设在创建SomeResource的时候可能会有异常,那么当p_指向的资源被创建但p2_指向的资源创建失败时,Example的实例就整个创建失败,那么p_指向的资源就存在内存泄露问题。

用下边的这个方法可以为权宜之计:

 

  1. Example() : p_(0),p2_(0)  
  2. {  
  3. try {  
  4. p_=new SomeResource();  
  5. p2_=new SomeResource("H",true);  
  6. std::cout << "Creating Example, allocating SomeResource!\n";  
  7. }  
  8. catch(...) {  
  9. delete p2_;  
  10. delete p_;  
  11. throw;  
  12. }  

 

但是我们可以利用一个对象在离开一个域中会调用析构函数的特性,在构造函数中完成初始化,在析构函数中完成清理工作,将需要操作和保护的指针作为成员变量放入RAII中。

 

  1. template   
  2. class RAII {  
  3. T* p_;  
  4. public:  
  5. explicit RAII(T* p) : p_(p) {}  
  6. ~RAII() {  
  7. delete p_;  
  8. }  
  9. void reset(T* p) {  
  10. delete p_;  
  11. p_=p;  
  12. }  
  13. T* get() const {  
  14. return p_;  
  15. }  
  16. T& operator*() const {  
  17. return *p_;  
  18. }  
  19. void swap(RAII& other) {  
  20. std::swap(p_,other.p_);  
  21. }  
  22. private:  
  23. RAII(const RAII& other);  
  24. RAII& operator=(const RAII& other);  
  25. }; 

我们在具体使用把保护的指针Someresource放在RAII中:

 

 

  1. class Example {  
  2. RAII p_;  
  3. RAII p2_;  
  4. public:  
  5. Example() :  
  6. p_(new SomeResource()),  
  7. p2_(new SomeResource()) {}  
  8. Example(const Example& other)  
  9. : p_(new SomeResource(*other.p_)),  
  10. p2_(new SomeResource(*other.p2_)) {}  
  11. Example& operator=(const Example& other) {  
  12. // Self assignment?  
  13. if (this==&other)  
  14. return *this;  
  15. *p_=*other.p_;  
  16. *p2_=*other.p2_;  
  17. return *this;  
  18. }  
  19. ~Example() {  
  20. std::cout << "Deleting Example, freeing SomeResource!\n";  
  21. }  
  22. }; 

现在即使p_成功而p2_失败,那么在Stack winding时也会调用RAII的析构函数保证了p_指向的Someresource被析构。这种方法较之例1中需要实现被组合的指针类型相应的接口不同,这里不需要对接口进行封装。当然,在例1中,你也可以提供一个getPointer的函数直接将句柄提供出来。

其实在Example中,已经不需要析构函数了,因为RAII类会帮它照顾好这一切的。这有点像auto_ptr,本文并不打算深入讨论智能指针这个话题。

#p#

3)锁操作

 

  1. /*  
  2. * =====================================================================================  
  3. *  
  4. * Filename: threadlock.cpp  
  5. *  
  6. * Description: Lock for RAII  
  7. *  
  8. * Version: 1.0  
  9. * Created: 05/09/2011 10:16:13 PM  
  10. * Revision: none  
  11. * Compiler: g++  
  12. *  
  13. * Author: gnuhpc (http://blog.csdn.net/gnuhpc), warmbupt@gmail.com  
  14. *  
  15. * =====================================================================================  
  16. */ 
  17. #include   
  18. #include   
  19. #include   
  20. int counter = 0;  
  21. void* routine(void *ptr);  
  22. pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;  
  23. class NonCopyable  
  24. {  
  25. public:  
  26. NonCopyable(){};  
  27. private:  
  28. NonCopyable (NonCopyable const &); // private copy constructor  
  29. NonCopyable & operator = (NonCopyable const &); // private assignment operator  
  30. };  
  31. class ScopeMutex:NonCopyable  
  32. {  
  33. public:  
  34. ScopeMutex(pthread_mutex_t* mutex):mutex_(mutex){  
  35. pthread_mutex_lock( mutex_ );  
  36. }  
  37. ~ScopeMutex(){  
  38. pthread_mutex_unlock( mutex_ );  
  39. }  
  40. private:  
  41. pthread_mutex_t *mutex_;  
  42. };  
  43. int main(int argc, char *argv[])  
  44. {  
  45. int rc1, rc2;  
  46. pthread_t thread1, thread2;  
  47. if( (rc1=pthread_create( &thread1, NULL, routine, NULL)) )  
  48. {  
  49. printf("Thread creation failed: %d\n", rc1);  
  50. }  
  51. if( (rc2=pthread_create( &thread2, NULL, routine, NULL)) )  
  52. {  
  53. printf("Thread creation failed: %d\n", rc1);  
  54. }  
  55. pthread_join( thread1, NULL);  
  56. pthread_join( thread2, NULL);  
  57. }  
  58. void* routine(void *ptr)  
  59. {  
  60. ScopeMutex scopeMutex(&mutex);  
  61. counter++;  
  62. printf("%d\n",counter);  

 

 

2.总结

RAII机制保证了异常安全,并且也为程序员在编写动态分配内存的程序时提供了安全保证。缺点是有些操作可能会抛出异常,如果放在析构函数中进行则不能将错误传递出去,那么此时析构函数就必须自己处理异常。这在某些时候是很繁琐的。

 

【编辑推荐】

  1. C/C++返回内部静态成员的陷阱
  2. 再驳Linus:思科工程师对C++不得不说的事
  3. C/C++是程序员必须掌握的语言吗?
  4. 浅析C++中的动态多维数组
  5. Visual C++中实现对图像数据的读取显示
责任编辑:于铁 来源: CSDN博客
相关推荐

2011-06-09 14:34:04

C++NVI

2011-06-09 14:52:09

Pimpl机制

2023-12-20 12:40:51

C++RAII编程

2010-01-25 18:24:11

C++

2009-08-19 09:57:01

C++ RAII

2023-11-22 12:25:05

C++RTTI

2010-02-01 17:19:30

C++运行机制

2010-02-02 15:30:05

C++ include

2010-02-04 11:23:25

C++反射机制

2010-02-06 14:04:58

C++内存使用机制

2010-01-13 11:14:06

C++虚表

2018-03-14 08:33:33

C++元编程云成本

2015-12-28 11:25:51

C++异常处理机制

2023-09-22 09:04:00

C++编程

2024-03-04 10:53:08

RAIIC++开发

2022-07-21 09:15:28

C++垃圾回收

2016-12-01 11:20:42

2011-06-07 08:58:31

javascript

2010-01-13 18:30:18

CC++程序设计

2010-01-28 09:54:27

C++程序设计
点赞
收藏

51CTO技术栈公众号