当前位置: 首页 > article >正文

跟我学C++中级篇——RAII

一、什么是RAII

Resource Acquisition Is Initialization,资源获取即初始化。C/C++的开发者都知道,在这类语言的开发中,内存需要手动来控制。也就是说,释放和回收内存得开发者亲历亲为。从某种角度看,能够把控内存的细节,当然是更灵活,可如果把控的不好,却是一场灾难。
根据二八定律,对于绝大多数的开发者来说,都被划分到了灾难的一个方向(即使经验丰富技术高超的开发者也难免会出问题)。那么,尤如医学上所讲,预防大于治疗。在C++中,哪些对象的生命周期是不需要自己控制而由系统自动控制呢?很容易想到类的局部对象。c++之父Bjarne Stroustrup因此提出了上面的RAII,

二、作用

RAII的作用非常明显,就是管理资源。通过临时的对象自动调用析构函数来处理资源。既然这样,初始化资源就可以放到构造函数中,由开发者来进行控制并交由系统自动创建。而此处的资源很容易想到的就是内存。但其实不仅有内存还有相关的句柄、IO等等都可以。RAII其实可以显著的降低因为经验或者疏忽导致的资源泄露。
常见的RAII应用的场景有以下几种情况:
1、内存管理
2、锁管理
3、句柄管理
4、IO管理
5、其它
建议初学者一定要掌握这个技巧,则可以大幅的提高在开发过程中资源管理的安全性。RAII当然也有一些不足之处,最典型的缺点就是其的优势。假如一个资源只使用一下便可以回收。那么封装进入一个对象后,则其生命周期则升级到了这个对象的作用域生命周期。而且有可能无意间的循环引用和对象与资源生命周期的不同情况下,都可能会有一些细节的问题需要完善。

三、与三五原则的关系

在RAII中有一个比较典型的错误应用,它一般类似于下面的样子:

class Lock
{
public:
    Lock()
    {
        //相关锁初始化 
    }
 
    ~Lock()
    {
        //相关锁回收
    }
 
private:
    //Lock(const Lock &);
    //Lock operator =(const Lock &);
};
 
void UseLock(Lock l)//注意此处
{
//此处使用Lock来控制锁
}
unsigned  ThreadFun(){
  Lock l;
  UseLock(l);
}  

RAII如果类似上面的方法使用,即在RAII应用时再增加一层控制,发现锁就不好使了。难道RAII不能再封装?并不是,而是锁没有被深拷贝导致的UseLock函数中的复制的l并没有被拷贝出一个相同的副本,值传递导致在UseLock后其内部的l副本和ThreadFun中使用l共用一个锁资源。而前者在函数退出后会调用析构函数释放掉锁。这样在后面的Lock对象再使用形同虚设。在前面分析过三五原则,这个错误其实就是没有正确处理这个原则的非常典型的应用。

四、应用和例程

RAII的应用非常简单,就是以下几个步骤:
1、设计并开发一个类来封装相关的资源
2、在类构造函数中创建资源
3、在类的析构函数中释放资源
4、在实际的作用域空间内创建此类的临时对象
下面就看一个简单的锁的例子:

template <typename Mutex_> class lock_guard final {
public:
  lock_guard(Mutex_ *m_rwMt) : m_mt(m_rwMt) { pthread_mutex_lock(m_mt); }
  ~lock_guard() { pthread_mutex_unlock(m_mt); }

public:
  lock_guard(const lock_guard &) = delete;
  lock_guard(lock_guard &&) = delete;
  lock_guard &operator=(const lock_guard &) = delete;
  lock_guard &operator=(lock_guard &&) = delete;

private:
  Mutex_ *m_mt;
};

其实内存控制也和这个差不多,只要把相关的内存创建和回收控制好即可。
在STL的标准库中,很多实现都应用到了RAII,比如常见的lock_guard,智能指针和stream等,所以还是建议大家把这个技巧弄清楚明白。

五、总结

记得以前总结过RAII相关的资料,可最近回头一看,没有找到 。于是,就只好将其再总结一篇,如果找到,就算重复总结吧。其实RAII更像是一种无奈之举,总得有一种安全的处理资源的方式啊。恰好,在C++中有这种变通的实现方式。其实上面还是有一些细节没有提到,比如创建和释放资源如果出现异常怎么办(即构造函数或析构函数如何处理异常)?这个在相关的知识中有过说明就不再详细阐述了。
或许这就是古人常说的,天无绝人之路!所以,对网上说的什么C++/c要被清退的说法,淡然处之即好。该来的一定会来,该走的也一定会走,不是谁说让谁走谁就会走。


http://www.kler.cn/a/397480.html

相关文章:

  • 【操作系统不挂科】<Linux进程概念(4)>选择题(带答案与解析)
  • 查询DBA_FREE_SPACE缓慢问题
  • springboot接口返回数据给前端,BigDecimal为null但返回前端显示-1
  • Python进程间通讯大揭秘:原理深度剖析与实战案例分享
  • 聊聊Flink:Flink的分区机制
  • 常用在汽车PKE无钥匙进入系统的高度集成SOC芯片:CSM2433
  • 51单片机基础 06 串口通信与串口中断
  • JAVA接入WebScoket行情接口
  • 高精度加法高精度乘法
  • 关键JavaScript进行表单验证:提升用户体验与数据完整性
  • 事务一致性的理解
  • 网络工程师教程第6版(2024年最新版)
  • 硬石电机学习2024116
  • element中封装axios如何实现请求函数自定义loading
  • 行车记录仪乱码解决方案与预防策略
  • 能源革命持续发力,华普微隔离器助力储能行业“向绿向新”
  • 米家电动牙刷拆解学习
  • MATLAB绘图
  • 【Linux】--环境变量
  • html5表单属性的用法
  • MySQL数据库1——数据库概论
  • Spring Boot 集成 RabbitMQ:消息生产与消费详解
  • MySQL初学之旅(3)约束
  • CentOS8 启动错误,enter emergency mode ,开机直接进入紧急救援模式,报错 Failed to mount /home 解决方法
  • Java 简单家居开关系统
  • HTML之表格学习记录