文章目录



1 前言

  C/C++相比其他高级编程语言,具有指针的概念,指针即是内存地址。C/C++可以通过指针来直接访问内存空间,效率上的提升是不言而喻的,是其他高级编程语言不可比拟的;比如访问内存一段数据,通过指针可以直接从内存空间读取数据,避免了中间过程函数压栈、数据拷贝甚至消息传输等等。



  内存交给程序员管理,是存在隐患的;申请了内存,因为意外原因而没有释放,即导致内存泄漏;系统长期运行后因申请不到可用内存而导致异常,甚至崩溃。为了避免内存泄漏,C/C++程序员发明各类方法以检测或者避免内存泄漏。RAII就是一个C++规范标准,遵循该标准,以尽可能避免内存泄漏。



2 什么是RAII

  RAII全称为​​Resource Acquisition Is Initialization​​,由C++发明者Bjarne Stroustrup提出的设计理念;中文可直译为资源获取即为初始化,是C++语言的一种管理资源、避免泄漏的方法标准。RAII基本原则是,资源与对象的生命周期绑定,利用C++类将由程序员管理的资源间接转换为由系统管理,程序员不需显式地释放资源,以从根本上避免内存泄漏,基本步骤包括:

  • 自动申请资源
  • 使用资源
  • 自动释放资源

  RAII实现过程易于理解,在创建一个对象是,构造函数用于申请资源空间,在对象生命周期内,资源可用正常访问;对象超出作用域或者生命周期结束后(释放),系统调用析构函数释放已申请的资源。



3 为什么用RAII

  • 将由程序员管理的资源转换为由系统管理
  • 避免内存泄漏
  • 良好的编程约束标准


4 RAII应用

  RAII典型的应用例子就是C++11引入的智能指针、类模板锁,以解决内存泄漏、死锁问题。

  • 智能指针
  • 类模板lock_guard


  ​​以一个“申请—释放”内存的最常用过程为例:​

  • 常规写法,确保函数所有出口都释放申请的内存
int main(int argc, char * * argv)
{
int *p = NULL;
bool condition0 = false;
bool condition1 = false;

p = new int();

/* todo */

if (condition0)
{
delete p;
return -1;
}

if (condition1)
{
delete p;
return -1;
}

delete p;

return 0;
}
  • 因为中途退出函数,(忘记)未释放内存,导致内存泄漏
int main(int argc, char * * argv)
{
int *p = NULL;
bool condition0 = false;
bool condition1 = false;

p = new int();

/* todo */

if (condition0)
{
return -1; /* 可能导致内存泄漏 */
}

if (condition1)
{
return -1 ; /* 可能导致内存泄漏 */
}

delete p;

return 0;
}
  • 引入RAII类,将内存交给系统管理
class new_raii
{
public:
explicit new_raii(std::function<void()> fun):delete_fun(fun)
{
std::cout << "call constrcutor fun" << std::endl;
}

~new_raii()
{
std::cout << "call destrcutor fun" << std::endl;
delete_fun(); /* 调用释放资源函数 */
}
private:
std::function<void()> delete_fun;
};
  • RAII完整示例
#include <iostream>
#include <functional>

class new_raii
{
public:
explicit new_raii(std::function<void()> fun):delete_fun(fun)
{
std::cout << "call constrcutor fun" << std::endl;
}

~new_raii()
{
std::cout << "call destrcutor fun" << std::endl;
delete_fun(); /* 调用释放资源函数 */
}
private:
std::function<void()> delete_fun;
};

int main(int argc, char * * argv)
{
int *p = NULL;
bool condition0 = true;
bool condition1 = false;

p = new int();

/* 将申请内存交给raii类对象管理 */
new_raii([&]
{
std::cout << "call delete fun" << std::endl;
delete p;
} /* 指定删除资源函数 */
);

/* todo */
if (condition0)
{
return -1;
}

if (condition1)
{
return -1;
}

return 0;
}

  编译执行结果:

acuity@ubuntu:/home/RAII$ g++ raii.cpp -o raii -std=c++11
acuity@ubuntu:/home/RAII$ ./raii
call constrcutor fun
call destrcutor fun
call delete fun
acuity@ubuntu:/home/RAII$

  从结果看,无论函数何时退出,都无需显示调用​​delete​​释放申请的内存。只要类对象生命周期结束,即调用析构函数释放内存。



5 小结

  RAII本质是将资源和对象生命周期绑定,将资源管理任务转化为对象管理任务,资源的申请和释放由系统自动调用构造和析构函数实现;将程序员管理的资源间接转为由系统管理。

  内存是系统资源之一,除此之外,其他系统资源如申请了,没有及时释放,同样会导致“资源泄漏”。对于其他系统资源,也可以参考RAII原则,确保系统的稳定性。内存是最直接接触的资源,不限于内存,常用系统资源及异常现象包括:

  • 文件描述符fd
      open了文件描述符,没有及时close,导致系统文件描述符用尽。
  • 互斥锁
      互斥锁资源的使用不当,导致死锁产生。
  • socket套接字
      socket套接字用尽,导致创建socket失败。
  • 端口、进程、线程、文件等等有限的资源