RAII
是指
C++
语言中资源管理的一种方式,简单而又不会出什么岔子,英文全拼也说明了他的用法,
它是“
R
esource A
cquisition I
s I
nitialization
”的首字母缩写
首先让我们来明确资源的概念,在计算机系统中,资源是数量有限且对系统正常运转具有一定作用的元素。比如,内存,文件句柄,网络套接字(
network sockets
),互斥锁(
mutex locks
)等等,它们都属于系统资源。由于资源的数量不是无限的,有的资源甚至在整个系统中仅有一份,因此我们在使用资源时必须严格遵循的步骤是:
1.
获取资源
2.
使用资源
3.
释放资源
例如在下面的
UseFile
函数中:
void UseFile(char const* fn)
{
FILE* f = fopen(fn, "r"); // 获取资源
// 在此处使用文件句柄f...代码 // 使用资源
fclose(f); // 释放资源
}
调用
fopen()
打开文件就是获取文件句柄资源,操作完成之后,调用
fclose()
关闭文件就是释放该资源。资源的释放工作至关重要,如果只获取而不释放,那么资源最终会被耗尽。上面的代码是否能够保证在任何情况下都调用
fclose
函数呢?请考虑如下情况:
void UseFile(char const* fn)
{
FILE* f = fopen(fn, "r"); // 获取资源
// ........... 使用资源代码
if (!g()) return; // 如果操作g失败!
// ...
if (!h()) return; // 如果操作h失败!
// ...
fclose(f); // 释放资源
}
但是在使用文件
f
的过程中,因某些操作失败而造成函数提前返回的现象经常出现。这时函数
UseFile
的执行流程将变为:
很明显,这里忘记了一个重要的步骤:在操作
g
或
h
失败之后,
UseFile
函数必须首先调用
fclose()
关闭文件,然后才能返回其调用者,否则会造成资源泄漏。因此,需要将
UseFile
函数修改为:
void UseFile(char const* fn)
{
FILE* f = fopen(fn, "r"); // 获取资源
// 使用资源
if (!g()) { fclose(f); return; }
// ...
if (!h()) { fclose(f); return; }
// ...
fclose(f); // 释放资源
}
现在的问题是:用于释放资源的代码
fclose(f)
需要在不同的位置重复书写多次。如果再加入异常处理,情况会变得更加复杂。例如,在文件
f
的使用过程中,程序可能会抛出异常:
void UseFile(char const* fn)
{
FILE* f = fopen(fn, "r"); // 获取资源
// 使用资源
try {
if (!g()) { fclose(f); return; }
// ...
if (!h()) { fclose(f); return; }
// ...
}
catch (...) {
fclose(f); // 释放资源
throw;
}
fclose(f); // 释放资源
}
我们必须依靠
catch(...)
来捕获所有的异常,关闭文件
f
,
并重新抛出该异常。随着控制流程复杂度的增加,需要添加资源释放代码的位置会越来越多。如果资源的数量还不止一个,那么程序员就更加难于招架了。可以想象
这种做法的后果是:代码臃肿,效率下降,更重要的是,程序的可理解性和可维护性明显降低。是否存在一种方法可以实现资源管理的自动化呢?答案是肯定的。假
设
UseResources
函数要用到
n
个资源,则进行资源管理的一般模式为:
void UseResources() // 注意不要使用指针(智能指针可以)
{
// 获取资源1
// ...
// 获取资源n
// 使用这些资源
// 释放资源n
// ...
// 释放资源1
}
不难看出资源管理技术的关键在于:要保证资源的释放顺序与获取顺序严格相反。这自然使我们联想到局部对象的创建和销毁过程。在
C++
中,定义在栈空间上的局部对象称为自动存储(
automatic memory
)对象。管理局部对象的任务非常简单,因为它们的创建和销毁工作是由系统自动完成的。我们只需在某个作用域(
scope
)中定义局部对象(这时系统自动调用构造函数以创建对象),然后就可以放心大胆地使用之,而不必担心有关善后工作;当控制流程超出这个作用域的范围时,系统会自动调用析构函数,从而销毁该对象。
如果系统中的资源也具有如同局部对象一样的特性,自动获取,自动释放,那该有多么美妙啊!。事实上,您的想法已经与
RAII
不谋而合了。既然类是
C++
中的主要抽象工具,那么就将资源抽象为类,用局部对象来表示资源,把管理资源的任务转化为管理局部对象的任务。这就是
RAII
惯用法的真谛!可以毫不夸张地说,
RAII
有效地实现了
C++
资源管理的自动化。例如,我们可以将文件句柄
FILE
抽象为
FileHandle
类:
class FileHandle {
public:
FileHandle(char const* n, char const* a) { p = fopen(n, a); }
~FileHandle() { fclose(p); }
private:
FileHandle(FileHandle const&);
FileHandle& operator= (FileHandle const&); // 禁止拷贝操作
FILE *p;
};
FileHandle
类的构造函数调用
fopen()
获取资源;
FileHandle
类的析构函数调用
fclose()
释放资源。请注意,考虑到
FileHandle
对象代表一种资源,它并不具有拷贝语义,因此我们将拷贝构造函数和赋值运算符声明为私有成员。如果利用
FileHandle
类的局部对象表示文件句柄资源,那么前面的
UseFile
函数便可简化为:
void UseFile(char const* fn)
{
FileHandle file(fn, "r");
// 在此处使用文件句柄f...
// 超出此作用域时,系统会自动调用file的析构函数,从而释放资源
}
现在我们就不必担心隐藏在代码之中的
return
语句了;不管函数是正常结束,还是提前返回,系统都必须“乖乖地”调用
f
的析构函数,资源一定能被释放。
且慢!如若使用文件
file
的代码中有异常抛出,难道析构函数还会被调用吗?此时
RAII
还能如此奏效吗?问得好。事实上,当一个异常抛出之后,系统沿着函数调用栈,向上寻找
catch
子句的过程,称为栈辗转开解(
stack unwinding
)。
C++
标准规定,在辗转开解函数调用栈的过程中,系统必须确保调用所有已创建起来的局部对象的析构函数。例如:
void Foo()
{
FileHandle file1("n1.txt", "r");
FileHandle file2("n2.txt", "w");
Bar(); // 可能抛出异常
FileHandle file3("n3.txt", "rw")
}
当
Foo()
调用
Bar()
时,局部对象
file1
和
file2
已经在
Foo
的函数调用栈中创建完毕,而
file3
却尚未创建。如果
Bar()
抛出异常,那么
file2
和
file1
的析构函数会被先后调用(注意:析构函数的调用顺序与构造函数相反);由于此时栈中尚不存在
file3
对象,因此它的析构函数不会被调用。只有当一个对象的构造函数执行完毕之后,我们才认为该对象的创建工作已经完成。栈辗转开解过程仅调用那些业已创建的对象的析构函数。
RAII
惯用法同样适用于需要管理多个资源的复杂对象。例如,
Widget
类的构造函数要获取两个资源:文件
myFile
和互斥锁
myLock
。每个资源的获取都有可能失败并且抛出异常。为了正常使用
Widget
对象,这里我们必须维护一个不变式(
invariant
):当调用构造函数时,要么两个资源全都获得,对象创建成功;要么两个资源都没得到,对象创建失败。获取了文件而没有得到互斥锁的情况永远不能出现,也就是说,不允许建立
Widget
对象的“半成品”。如果将
RAII
惯用法应用于成员对象,那么我们就可以实现这个不变式:
class Widget {
public:
Widget(char const* myFile, char const* myLock)
: file_(myFile), // 获取文件myFile
lock_(myLock) // 获取互斥锁myLock
{}
// ...
private:
FileHandle file_;
LockHandle lock_;
};
FileHandle
和
LockHandle
类的对象作为
Widget
类的数据成员,分别表示需要获取的文件和互斥锁。资源的获取过程就是两个成员对象的初始化过程。在此系统会自动地为我们进行资源管理,程序员不必显式地添加任何异常处理代码。
RAII
的本质
内容是用对象代表资源,把管理资源的任务转化为管理对象的任务,将资源的获取和释放与对象的构造和析构对应起来,从而确保在对象的生存期内资源始终有效,对象销毁时资源必被释放。换句话说,拥有对象就等于拥有资源,对象存在则资源必定存在。
由此可见,
RAII
惯用法是进行资源管理的有力武器。
C++
程序员
依靠
RAII
写出的代码不仅简洁优雅,而且做到了异常安全。
本文截取自:http://www.cnblogs.com/hsinwang/articles/214663.html
分享到:
相关推荐
主要介绍了C++中的RAII机制详解,RAII是Resource Acquisition Is Initialization的简称,是C++语言的一种管理资源、避免泄漏的惯用法,需要的朋友可以参考下
1.2 C++中的健壮指针和资源管理 1.2.1 第一条规则(RAII) 1.2.2 Smart Pointers 1.2.3 Resource Transfer 1.2.4 Strong Pointers 1.2.5 Parser 1.2.6 Transfer Semantics 1.2.7 Strong Vectors 1.2.8 Code ...
1.简介 ...智能指针是C++程序员们一件管理内存的利器,使用智能指针管理内存资源,实际上就是将申请的内存资源交由智能指针来管理,是RAII技术的一种实现。RAII是C++的之父Bjarne Stroustrup教授提
如此c++引入 智能指针 ,智能指针即是C++ RAII的一种应用,可用于动态资源管理,资源即对象的管理策略。 智能指针在 标头文件的 std 命名空间中定义。 它们对 RAII 或 获取资源即初始化 编程惯用法至关重要。 RAII ...
unique_resource,用于独家所有权资源管理的通用 RAII 包装器。 这是使用 Boost 软件许可证 1.0 的 unique_resource 实现。 此实现基于 C++ 标准委员会论文中的示例实现。 什么是 unique_resource unique_resource...
RAII是C++的发明者Bjarne Stroustrup提出的概念,RAII全称是"Resource Acquisition is Initialization",直译过来是"资源获取即初始化",也 就是说在构造函数中申请分配资源,在析构函数中释放资源。因为C++的语⾔...
基于TensorRT的C++高性能推理库源码+使用说明(支持RT-DETR,Yolov5、v7、v8、yoloX、OSTrack、LightTrack等) Highlights - 支持全景驾驶感知 YOLOPv2,目标检测 RT-DETR,Yolo 5/X/7/8 ,多目标跟踪 Bytetrack,单...
C++ 管理资源的式和其它语样,堆内存中存放“不易失”的数据,底层由 malloc 和 free 进管理同样使 `new` 关键字于堆上分配内存,但需注意的是,
智能指针主要思想是RAII思想,"使⽤对象管理资源",在类 的构造函数中获取资源,在类的析构函数中释放资源。智能指针的⾏为类似常规指针,重要的区别是它负责⾃动释放所指向的对象。 RAII是Resource Acquisition Is ...
描述:封装最底层的系统调用,大多数使用RAII手法管理资源 规划:随着编程经验的增加,可以在保留接口不变的情况下修改内部资源 构件组成: Alloc:创建一个内存池,用以维护LogBuffer与UserbBuffer,在Log或者Tcp...
使用RAII手法封装互斥器(pthrea_mutex_t)、 条件变量(pthread_cond_t)等线程同步互斥机制,使用RAII管理文件描述符等资源 使用shared_ptr、weak_ptr管理指针,防止内存泄漏 下一步开发计划 添加异步日志系统,...
条款36 特定干类的内存管理·············································........................... 93 条款37 数组分配..........................................
**②同步机制实现**:基于unique_lock以及condition_variable实现同步和互斥,符合RAII原则;\ **5)简单客户端**:(可以通过浏览器进行服务端访问,也可以通过该客户端实现交互以及非活动连接处理的测试)\ **...
范围保护在其 C++ 析构函数中执行块并确保正确的资源管理,即使已抛出异常。 本课程向 iOS 开发人员的世界介绍了 RAII 习语。 License : BSD为什么是 ObjcScopedGuard ? 正确的手动资源处理资源是一项艰巨的工作,...
01 |堆,栈,RAII:C ++里该如何管理资源?自己动手,实现C ++的智能指针03 |04 |右值和移动究竟解决了什么问题?容器汇编I:比较简单的几个容器/ L- Q(}。a9 W- j9 ^ 4 W 05 |容器汇编II:需要函数对象的容器't6 ...
它提供了RAII样式的类来自动执行资源管理(libzmq C API要求用户注意显式释放资源) cppzmq是轻量级的仅标头绑定。 您只需要包括头文件zmq.hpp(可能还有zmq_addon.hpp)即可使用它。 zmq.hpp旨在包含libzmq C ...