1 RAII介绍
RAII全称是Resource Acquisition Is Initialization,翻译过来是资源获取即初始化,RAII机制用于管理资源的申请和释放。对于资源,我们通常经历三个过程,申请,使用,释放,这里的资源不仅仅是内存,也可以是文件、socket、锁等等。但是我们往往只关注资源的申请和使用,而忘了释放,这不仅会导致内存泄漏,可能还会导致业务逻辑的错误,RAII就用来解决此类问题。
2 C++中的RAII使用
我们看以下例子。
std::mutex m;
void fn()
{
m
.
lock
(
)
;
使用资源
m
.
unlock
(
)
;
}
上面的代码是对互斥变量的使用,我们看到加锁和解锁是成对出现的。如果我们忘了unlock那么别的线程再也无法枷锁成功,而且还会导致一直阻塞。我们看C++怎么解决这个问题。
std::mutex m;
void fn(){
std
::
lock_guard
<
std
::
mutex
>
guard
(
m
)
;
do_something
(
)
;
// 指向完函数后,guard会被析构,从而mutex也会被释放
}
我们看到上面的代码中,我们只需要加锁,操作资源,不需要手动解锁。那么RAII是怎么做的呢?我们看看lock_guard的实现。
template
<
class Mutex
>
class lock_guard
{
private
:
Mutex
&
mutex_
;
public
:
lock_guard
(
Mutex
&
mutex
)
:
mutex_
(
mutex
)
{
mutex_
.
lock
(
)
;
}
~
lock_guard
(
)
{
mutex_
.
unlock
(
)
;
}
// 禁止复制和赋值
lock_guard
(
lock_guard
const
&
)
=
delete
;
lock_guard
&
operator
=
(
lock_guard
const
&
)
=
delete
;
}
;
我们看到实现很简单,在创建一个lock_guard对象的时候,lock_guard会初始化内部字段,并且执行加锁操作。当lock_guard析构的时候,会指向解锁操作,所以借助这个类,我们就不需要关注解锁的操作了,具体的原理是利用了C++对象离开作用域后会自定执行析构函数。
3 RAII实践
3.1 文件管理
理解了使用和原理后,我们也可以利用RAII机制编写自己的类。
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
class FD
{
private
:
int
fd
;
public
:
FD
(
const
char
*
s
)
{
fd
=
open
(
s
,
O_RDONLY
)
;
printf
(
"构造函数:打开文件的文件描述符:%d\n"
,
fd
)
;
}
~
FD
(
)
{
int
ret
=
close
(
fd
)
;
printf
(
"析构函数:关闭文件执行结果:%d\n"
,
ret
)
;
}
// 重载*运算符
int
operator
*
(
)
{
return
fd
;
}
FD
(
FD
const
&
)
=
delete
;
FD& operator=(FD const&) = delete;
};
int main(){
const
char
*
filePath
=
"/dev/null"
;
FD
fd
(
filePath
)
;
// 操作资源
printf
(
"文件描述符:%d\n"
,
*
fd
)
;
return
0
;
// 离开作用域,自动析构,关闭fd
}
我们封装了一个文件管理类,这样我们就可以只关注打开资源,操作资源而不需要手动关闭资源了。
3.2 智能指针
我们再看一下RAII的使用场景,就是智能指针的实现。
#include<iostream>
#include<stdio.h>
using namespace std
;
template
<
class T
>
class SmartPoint
{
T
*
point
;
public
:
SmartPoint
(
T
*
ptr
=
nullptr
)
:
point
(
ptr
)
{
}
~
SmartPoint
(
)
{
if
(
point
)
{
// 会调用point指向对象的的析构函数
delete point
;
}
}
// 使用智能指针就像使用内部包裹的的对象一样
T
&
operator
*
(
)
{
return
*
point
;
}
T
*
operator
->
(
)
{
return
point
;
}
};
class Demo
{
public
:
Demo
(
)
{
printf
(
"执行构造函数\n"
)
;
}
~
Demo
(
)
{
printf
(
"执行析构函数\n"
)
;
}
void
show
(
)
{
printf
(
"hello\n"
)
;
}
};
int main() {
SmartPoint
<
Demo
>
smartPoint
(
new
Demo
(
)
)
;
smartPoint
->
show
(
)
;
Demo
&
demo
=
*
smartPoint
;
demo.show();
}
执行上面代码输出
执行构造函数
hello
hello
执行析构函数
我们看到SmartPoint的实现中,在初始化SmartPoint的时候会传入被管理的对象,并通过重载*和->运算符实现对包裹对象的使用,最后在SmartPoint被析构的时候,也会把包裹对象的内存给析构掉。
4 RAII在Rust的应用
RAII机制和智能指针不仅在C++中使用,在新语言Rust中,同样用到了该技术。
struct Demo(u32);
// 实现Drop trait跟踪Demo的行为
impl Drop
for
Demo
{
fn
drop
(
&
mut self
)
{
println
!
(
"执行析构"
)
;
}
}
fn
main
(
)
{
// 分配堆内存
let demo_box
=
Box
::
new
(
Demo
(
1
)
)
;
// 操作资源
println
!
(
"{}"
,
demo_box
.
0
)
;
// 自动析构
}
执行上面代码输出
1
执行析构
Box就是Rust中的智能指针,使用的方式和C++中类似,初始化Box时传入一个对象,然后交给Box管理,使用demo_box就像是要Demo结构体一样。最后在函数执行完时包裹对象的内存会被释放。