C++之RAII

RAII是resource acquisition is initialization的缩写,意为“资源获取即初始化”。它是C++之父Bjarne Stroustrup提出的设计理念,其核心是把资源和对象的生命周期绑定,对象创建获取资源,对象销毁释放资源。在RAII的指导下,C++把底层的资源管理问题提升到了对象生命周期管理的更高层次。
    说起来,RAII的含义倒也不算复杂。用白话说就是:在类的构造函数中分配资源,在析构函数中释放资源。这样,当一个对象创建的时候,构造函数会自动地被调用;而当这个对象被释放的时候,析构函数也会被自动调用。于是乎,一个对象的生命期结束后将会不再占用资源,资源的使用是安全可靠的。
C++ RAII体现出了简洁、安全、实时的特点:

1.概念简洁性:让资源(包括内存和非内存资源)和对象的生命周期绑定,资源类的设计者只需用在类定义内部处理资源问题,提高了程序的可维护性
2.类型安全性:通过资源代理对象包装资源(指针变量),并利用运算符重载提供指针运算方便使用,但对外暴露类型安全的接口
3.异常安全性:栈语义保证对象析构函数的调用,提高了程序的健壮性
4.释放实时性:和GC相比,RAII达到了和手动释放资源一样的实时性,因此可以承担底层开发的重任

也许你还在惊讶RAII如此简单的时候,关于RAII的主要内容已经介绍完了。简单不意味着简陋,在我看来RAII虽然不像GC一样,是一套具体的机制,但它蕴含的对象与资源关系的哲学深度的理解却使得我对Bjarne Stroustrup肃然起敬!

最后,不得不提醒RAII的理念固然简单,不过在具体实现的时候仍有需要小心的地方。比如对于STL的auto_ptr,可以视为资源的代理对象,auto_ptr对象间的赋值是一个需要特别注意的地方。简单说来资源代理对象间赋值的语义不满足“赋值相等”,其语义是资源管理权的转移。

什么是“赋值相等”呢?比如:

int a;  int b = 10;  a = b; //这句话执行后 a == b 但对于资源代理对象,这是不满足的,比如:

auto_ptr<int> a(null);  auto_ptr<int> b(new int(123));  a = b; //这句话执行后a != b,赋值的语义是b把资源的管理权交给了a 

auto_ptr是这样一种指针:它是“它所指向的对象”的拥有者。这种拥有具有唯一性,即一个对象只能有一个拥有者,严禁一物二主。当auto_ptr指针被摧毁时,它所指向的对象也将被隐式销毁,即使程序中有异常发生,auto_ptr所指向的对象也将被销毁。

关于auto_ptr的几种注意事项:
1、auto_ptr不能共享所有权。
2、auto_ptr不能指向数组
3、auto_ptr不能作为容器的成员。
4、不能通过赋值操作来初始化auto_ptr
  std::auto_ptr<int> p(new int(42));     //OK
  std::auto_ptr<int> p = new int(42);    //ERROR
 这是因为auto_ptr 的构造函数被定义为了explicit
5、不要把auto_ptr放入容器

举个常见的例子:

[cpp] view plaincopy

  1. void Func()

  2. {

  3. FILE *fp;

  4. char* filename = "test.txt";

  5. if((fp=fopen(filename,"r"))==NULL)

  6. {

  7. printf("not open");

  8. exit(0);

  9. }

  10. ... // 如果 在使用fp指针时产生异常 并退出

  11. // 那么 fp文件就没有正常关闭
  12. fclose(fp);

  13. }

在资源的获取到释放之间,我们往往需要使用资源,但常常一些不可预计的异常是在使用过程中产生,就会使资源的释放环节没有得到执行。

此时,就可以让RAII惯用法大显身手了。

RAII的实现原理很简单,利用stack上的临时对象生命期是程序自动管理的这一特点,将我们的资源释放操作封装在一个临时对象中。

具体示例代码如下:

[cpp] view plaincopy

  1. class Resource{};

  2. class RAII{

  3. public:

  4. RAII(Resource* aResource):r_(aResource){} //获取资源

  5. ~RAII() {delete r_;} //释放资源

  6. Resource* get()    {return r_ ;} //访问资源

  7. private:

  8. Resource* r_;

  9. };

比如文件操作的例子,我们的RAII临时对象类就可以写成:

[cpp] view plaincopy

  1. class FileRAII{

  2. public:

  3. FileRAII(FILE* aFile):file_(aFile){}

  4. ~FileRAII() { fclose(file_); }//在析构函数中进行文件关闭

  5. FILE* get() {return file_;}

  6. private:

  7. FILE* file_;

  8. };

则上面这个打开文件的例子就可以用RAII改写为:

[cpp] view plaincopy

  1. void Func()

  2. {

  3. FILE *fp;

  4. char* filename = "test.txt";

  5. if((fp=fopen(filename,"r"))==NULL)

  6. {

  7. printf("not open");

  8. exit(0);

  9. }

  10. FileRAII fileRAII(fp);

  11. ... // 如果 在使用fp指针时产生异常 并退出

  12. // 那么 fileRAII在栈展开过程中会被自动释放,析构函数也就会自动地将fp关闭
  13. // 即使所有代码是都正确执行了,也无需手动释放fp,fileRAII它的生命期在此结束时,它的析构函数会自动执行!

  14. }

这就是RAII的魅力,它免除了对需要谨慎使用资源时而产生的大量维护代码。在保证资源正确处理的情况下,还使得代码的可读性也提高了不少。

创建自己的RAII类

一般情况下,RAII临时对象不允许复制和赋值,当然更不允许在heap上创建,所以先写下一个RAII的base类,使子类私有继承Base类来禁用这些操作:

[cpp] view plaincopy

  1. class RAIIBase

  2. {

  3. public:

  4. RAIIBase(){}

  5. ~RAIIBase(){}//由于不能使用该类的指针,定义虚函数是完全没有必要的
  6. RAIIBase (const RAIIBase &);

  7. RAIIBase & operator = (const RAIIBase &);

  8. void * operator new(size_t size);

  9. // 不定义任何成员

  10. };

当我们要写自己的RAII类时就可以直接继承该类的实现:

[cpp] view plaincopy

  1. template<typename T>

  2. class ResourceHandle: private RAIIBase //私有继承 禁用Base的所有继承操作

  3. {

  4. public:

  5. explicit ResourceHandle(T * aResource):r_(aResource){}//获取资源

  6. ~ResourceHandle() {delete r_;} //释放资源

  7. T *get()    {return r_ ;} //访问资源

  8. private:

  9. T * r_;

  10. };

将Handle类做成模板类,这样就可以将class类型放入其中。另外,
ResourceHandle可以根据不同资源类型的释放形式来定义不同的析构函数。

由于不能使用该类的指针,所以使用虚函数是没有意义的。

注:自己写的RAII类并没有经过大量的实践,可能存在问题,请三思而慎用。这里只是记录下自己的实现想法。

时间: 2024-10-18 20:07:53

C++之RAII的相关文章

RAII手法封装相互排斥锁

CriticalSectionWrapper是一个接口类 class CriticalSectionWrapper { public: // Factory method, constructor disabled static CriticalSectionWrapper* CreateCriticalSection(); virtual ~CriticalSectionWrapper() {} // Tries to grab lock, beginning of a critical se

RAII

全称是"Resource Acquisition is Initialization",翻译为资源获取就是初始化. 其实就是利用栈上的局部对象在离开作用域时会自动释放的原理,在临时对象创建时初始化资源,或者将资源交给对象管理,当临时对象析构时释放资源,因为临时对象在离开其作用时会自动析构,也就会自动释放其对应资源. 适用于对异常状况的处理,如果用try..catch或条件判断处理异常,除了在所有可能出现异常的位置要添加这些处理语句之外,还要手工的去释放相应的资源.有了RAII,只需要在

使用智能指针来管理对象 (基于RAII)

////一个简单的防止内存泄露的例子//void test() { //使用RAII的特性管理资源 //当智能指针unique_ptr被销毁时,它指向的对象也将被销毁 //这里test函数返回后 p将自动销毁 //unique_ptr<int[]> p( new int[200] ); //直接生成资源 //test函数返回后 p不能被正常销毁,就会造成资源泄露 //int* p = new int[200]; } int main() { while( 1 ) { test(); Sleep

浅谈RAII&智能指针

关于RAII,官方给出的解释是这样的"资源获取就是初始化".听起来貌似不是很懂的哈,其实说的通俗点的话就是它是一种管理资源,避免内存泄漏的一种方法.它可以保证在各种情况下,当你对对象进行使用时先通过构造函数来进行资源的分配和初始化,最后通过析构函数来进行清理,有效的保证了资源的正确分配和释放.(特别是在异常中,因为异常往往会改变代码正确的执行顺序,这就很容易引起资源管理的混乱和内存的泄漏) 其中智能指针就是RAII的一种实现模式,所谓的智能就是它可以自动化的来管理它所指向那份空间的资源

RAII惯用法

RAII,也称为“资源获取就是初始化”,是c++等编程语言常用的管理资源.避免内存泄露的方法.简单的说,RAII 的做法是使用一个对象,在其构造时获取资源,在对象生命期控制对资源的访问使之始终保持有效,最后在对象析构的时候释放资源. 例如,我们无需直接调用一对非成员函数OpenPort/ClosePort,而是可以考虑定义常性且内部初始化的RAII概念的“端口”操作类: class Port{ public: Port(const string& destination);//调用OpenPor

C++之RAII技术解析

1.什么是RAII 技术? 我们在C++中经常使用new申请了内存空间,但是却也经常忘记delete回收申请的空间,容易造成内存溢出,于是RAII技术就诞生了,来解决这样的问题.RAII(Resource Acquisition Is Initialization)机制是Bjarne Stroustrup首先提出的,是一种利用对象生命周期来控制程序资源(如内存.文件句柄.网络连接.互斥量等等)的简单技术. 我们知道在函数内部的一些成员是放置在栈空间上的,当函数返回时,这些栈上的局部变量就会立即释

Linux封装之四:RAII实现MutexLock自动化解锁

在实现线程的过程中,我们经常会写类似于这样的代码: { mutex_.lock(); //XXX if(...) 语句; //XXX mutex_.unlock(); } 虽然这段代码是正常的加锁解锁,但是有时候我们难免会出现一些低级错误,例如把 忘了写mutex_.unlock().那么我们该如何防止这种错误呢? 我们可以采用和实现智能指针相似的办法,把加锁和解锁封装在同一个对象中. 实现“对象生命期”等于“加锁.解锁周期” . 代码如下; 1 //类的关联 2 class MutexGuar

C++ 资源管理之 RAII

RAII,它是"Resource Acquisition Is Initialization"的首字母缩写.也称为"资源获取就是初始化",是c++等编程语言常用的管理资源.避免内存泄露的方法.它保证在任何情况下,使用对象时先构造对象,最后析构对象. RAII的好处在于它提供了一种资源自动管理的方式,当产生异常.回滚等现象时,RAII可以正确地释放掉资源. 当讲述C++资源管理时,Bjarne这样写道: 使用局部对象管理资源的技术通常称为"资源获取就是初始化

RAII&智能指针

智能指针是C++中为了实现资源的有效管理而被提出的,我们可以创建它但无须操心它的释放问题,在引入异常机制的程序里它是十分有用的,或者说,对于博主这中粗心大意的人来说还是可以偶尔使用的.他可以在一些场合防止内存泄漏的问题.但是,智能指针也是存在着许多的问题,所以许多的编程规范里告诫我们少使用智能指针,但对于我们来说,必须了解它的原理. *RAII:资源获得即初始化,我们在构造函数里将其初始化,并在析构函数里释放它 eg:一个简单的AutoPtr的实现 template<class T> clas

C++ RAII手法实现的线程安全的日志文件写例子

#include <iostream> #include <fstream> #include <string> #include <cstdarg> #include <cstdio> #include <pthread.h> // MutexLock 封装互斥锁的接口 class MutexLock { public: MutexLock() { pthread_mutex_init(&mutex_, NULL); } M