文章目录
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完整示例
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失败。 - 端口、进程、线程、文件等等有限的资源