RAII机制和智能指针

转载 发布者:薛洪言 发表于:2022-06-29

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结构体一样。最后在函数执行完时包裹对象的内存会被释放。


本文分享自微信公众号 - 编程杂技(theanarkh)。

声明:本文来自用户分享或转自网络,版权属于原作者,内容中的观点不代表编程技术网的观点。文章内容如有侵权,请联系管理员(QQ:3106529134)删除,本站将在一月内处理。
来源:VmpGYVYySXhWWGROVldoVllUSjRWbFpyV25kVWJIQlhWVzVPVGxKdVFsaFdSbEpIWVRKS1ZrMVVWbGRTZWtFeFdWVmFZVTVzV25SUFZsWlRaV3RHTkZkWGRHdFZNVXBHVDFaV1UyRjZSbk5aYTFaYVRWWmFSMWRzVG1oaVZscDVWRlpTWVZVeFpFZGpSMFphWWtkb2RsUlhlR3RrVjBZMlZXczFWMVpGV2xkV2JYaHZZekZXUjFkcmFHRlNSbXM1