导读:RAII是C++中一种管理资源、避免资源泄漏的惯用法,利用栈的特点来实现。本文较为详细介绍了RAII的原理、使用方法和优点,并且通过实例讲解了RAII在C++ STL中的应用,如智能指针和互斥锁等,在最后进行了编程实践。本文适合对C++编程有一定了解的开发者阅读。

1. 什么是RAII

RAII是Resource Acquisition Is Initialization的缩写,即“资源获取即初始化”。它是C++语言的一种管理资源、避免资源泄漏的惯用法,利用栈的特点来实现,这一概念最早由Bjarne Stroustrup提出。在函数中由栈管理的临时对象,在函数结束时会自动析构,从而自动释放资源,因此,我们可以通过构造函数获取资源,通过析构函数释放资源。即:


Object() {
// acquire resource in constructor
} ~Object() {
// release resource in destructor
}

RAII总结如下:

  • 将每一种资源封装在一个RAII类中:

    • 所有资源在构造函数中获取,例如:分配内存、打开文件、建立数据库连接等;如果无法完成则在构造函数中抛出异常;
    • 所有资源在析构函数中释放,例如:释放内存、关闭文件、销毁数据库连接等;不应该抛出任何异常。
  • 通过RAII类实例获取资源:

    • 具有自动生命管理周期或临时对象生命周期
    • 其生命周期与第一种绑定。

2. 为什么要使用RAII

我们知道,在C++中,通过new运算符动态申请内存,例如:

Foo* ptr = new Foo(1);

// ...
delete ptr;

在这段代码中,new运算符在计算机内存的堆上申请了一块Foo类型的内存,然后将其地址赋值给存储在栈上的指针ptr。为了能够释放内存资源,我们需要使用完new运算符申请的内存后,手动调用delete运算符释放内存。

但是,情况并不总是如此简单。

Foo* ptr = new Foo(1);

f(ptr);  // -->① may throw exception
if(ptr->g()) {
// ... --> ② may forget to delete ptr
return;
}
// ...
delete ptr;

如上面这个例子,我们可能会遇到以下几种情况:

  1. 忘记delete释放内存。比如释放原指针指向的内存前就改变了指针的指向。
  2. 程序抛出异常后导致无法delete。比如上面的①处,如果f函数抛出异常,没有机会运行delete,从而导致内存泄漏。
  3. 需求变更后,修改了函数,新增了分支,提前返回,却没有delete;现实情况代码复杂的话可能没有这么显而易见。

而通过RAII这样一种机制,我们可以使其自动释放内存。

3. C++ STL中RAII的应用

3.1 智能指针

智能指针是RAII的一种实现,它是一种模板类,用于管理动态分配的对象。智能指针的主要作用是自动释放内存,从而避免内存泄漏。C++11中提供了三种智能指针:unique_ptr、shared_ptr和weak_ptr。它们的详细原理将在之后的文章中介绍。这里我们以unique_ptr为例,它的构造函数如下:

template< class T, class Deleter = std::default_delete<T> > class unique_ptr;

unique_ptr的析构函数会自动释放内存,因此,我们可以通过unique_ptr来管理动态分配的内存,从而避免内存泄漏。例如:

std::unique_ptr<int> ptr = std::make_unique<int>(1); // release memory when ptr is out of scope

3.2 互斥锁

在多线程编程中,std::lock_guard, std::unique_lock, std::shared_lock等也利用了RAII的原理,用于管理互斥锁。当这些类的等对象创建时,会自动获取互斥锁;当对象销毁时,会自动释放互斥锁。

std::lock_guard的构造函数如下:

template< class Mutex > class lock_guard;

std::lock_guard的析构函数会自动释放互斥锁,因此,我们可以通过std::lock_guard来管理互斥锁,从而避免忘记释放互斥锁。例如:


std::mutex mtx;
std::lock_guard<std::mutex> lock(mtx); // unlock when lock is out of scope

不使用RAII的情况下,我们需要手动释放互斥锁,如下所示:

std::mutex mtx;
mtx.lock();
// ...
mtx.unlock();

3.3 文件操作

std::ifstream, std::ofstream等C++标准库的IO操作都是RAII的实现。

3.4 事务处理

数据库事务处理中,如果在事务结束时没有提交或回滚,就会导致数据库连接一直被占用,从而导致数据库连接池耗尽。因此,我们需要在事务结束时自动提交或回滚,从而释放数据库连接。这一过程也可以通过RAII来实现。

3.5 其他

RAII还可以用于管理其他资源,比如网络连接、线程等。

4. RAII的编程实践

基于RAII实现资源池的自动回收机制:

ResourcePool为资源池类,可以创建指定数量的资源,并提供获取和释放资源的接口。

ResourceWrapper为资源包装类,用于获取资源,并在对象销毁时自动释放资源。

Resource为资源类,用于模拟资源,通过id来标识,其构造函数和析构函数分别用于获取和释放资源。

代码实现如下:

#include <iostream>
#include <vector>
#include <deque>
constexpr int kErrorId = -1;
template<typename T>
class ResourcePool {
public:
ResourcePool(int size) {
for (int i = 0; i < size; ++i) {
pool_.emplace_back(i);
}
} T getResource() {
if (pool_.empty()) {
std::cout << "Resource is not available now." << std::endl;
return T();
}
T resource = std::move(pool_.front());
pool_.pop_front();
std::cout<< "Resource " << resource.ID() << " is acquired." << std::endl;
return resource;
} void releaseResource(T&& resource) {
if (resource.ID() == kErrorId) {
return;
}
std::cout << "Resource " << resource.ID() << " is released." << std::endl;
pool_.emplace_back(std::forward<T>(resource));
} private:
std::deque<T> pool_;
}; template<typename T>
class ResourceWrapper {
public:
ResourceWrapper(ResourcePool<T>& pool) : pool_(pool), resource_(pool_.getResource()) {
if(resource_.ID() == kErrorId) {
throw std::runtime_error("Resource is not available now.");
}
}
~ResourceWrapper() {
pool_.releaseResource(std::move(resource_));
}
ResourceWrapper(const ResourceWrapper& other) = delete;
ResourceWrapper& operator=(const ResourceWrapper& other) = delete; ResourceWrapper(ResourceWrapper&& other) noexcept : pool_(other.pool_), resource_(std::move(other.resource_)) {
} ResourceWrapper& operator=(ResourceWrapper&& other) noexcept {
pool_ = other.pool_;
resource_ = std::move(other.resource_);
return *this;
}
const T& GetRawResource() const noexcept{
return resource_;
}
private:
ResourcePool<T>& pool_;
T resource_;
}; class Resource {
public:
constexpr explicit Resource(int id) : id_(id) {
std::cout << "Resource " << id_ << " is created." << std::endl;
}
Resource(): id_(kErrorId) {}
~Resource() = default;
int ID() const {
return id_;
}
// delete copy constructor and copy assignment operator
Resource(const Resource& other) = delete;
Resource& operator=(const Resource& other) = delete; Resource(Resource&& other) noexcept : id_(other.id_) {
other.id_ = kErrorId;
} Resource& operator=(Resource&& other) noexcept {
id_ = other.id_;
other.id_ = kErrorId;
return *this;
}
private:
int id_;
}; constexpr int kPoolSize = 3;
ResourcePool<Resource> pool(kPoolSize); // Resource pool with 3 resources in global scope. void RequestRourceTest() {
constexpr int kResourcesNum = 3;
for (int i = 0; i < kResourcesNum; ++i) {
ResourceWrapper<Resource> resource_wrapper(pool);
resource_wrapper.GetRawResource();
}
} int main() {
RequestRourceTest();
return 0;
}

运行输出结果如下:

Resource 0 is created.
Resource 1 is created.
Resource 2 is created.
Resource 0 is acquired.
Resource 1 is acquired.
Resource 2 is acquired.
Resource 0 is released.
Resource 1 is released.
Resource 2 is released.

5. 总结

在本文中,我们介绍了C++中的RAII技术,它是一种管理资源的方法,可以帮助我们避免内存泄漏和资源泄漏等问题。RAII技术的核心思想是将资源的获取和释放绑定在对象的生命周期中,这样可以确保资源在不再需要时被正确释放。我们还介绍了如何使用RAII技术来管理动态内存、文件句柄和互斥锁等资源,并提供了一些示例代码来说明如何实现RAII类。最后,我们还讨论了RAII技术的一些注意事项和最佳实践,以帮助开发人员编写更安全、更可靠的代码。希望本文能够帮助您更好地理解和应用RAII技术。

在本文的编程实践中,还使用了std::move()、std::forward()、noexcept等诸多现代C++技术,更多细节和不足之处,将在之后的文章中进行进一步探讨。

参考:

  1. Effective C++, Item 13: Use objects to manage resources. Scott Meyers.
  2. https://en.cppreference.com/w/cpp/language/raii

你好,我是七昂,计算机科学爱好者,致力于分享C/C++、操作系统等计算机基础知识。希望我们能一起在计算机科学的世界里探索和成长,最终能站得更高,走得更远。如果你有任何问题或者建议,欢迎随时与我交流。感谢你们的支持和关注!

C++ | 每一个C++程序员都应该知道的RAII的更多相关文章

  1. 关于Unicode,字符集,字符编码,每个程序员都应该知道的事

    关于Unicode,字符集,字符编码,每个程序员都应该知道的事 作者:Jack47 李笑来的文章如何判断一个人是否聪明?中提到: 必要.清晰.且准确的概念,是一切思考的基石.所谓思考,很大程度上,就是 ...

  2. 每个JavaScript程序员都需要知道的5个数组方法

    Array.forEach() .forEach() 方法能够方便的让你 遍历数组里的每个元素,你可以在回调函数里对每个元素进行操作..forEach()方法没有返回值,你不需要在回调函数里写retu ...

  3. 每个php程序员都应该知道的15个最佳PHP库

    PHP是一种功能强大的web站点脚本语言,通过PHP,web网站开发者可以更容易地创建动态的引人入胜的web页面.开发人员可以使用PHP代码与一些网站模板和框架来提升功能和特性.然而,编写PHP代码是 ...

  4. 程序员必须要知道的Hadoop的一些事实

    程序员必须要知道的Hadoop的一些事实.现如今,Apache Hadoop已经无人不知无人不晓.当年雅虎搜索工程师Doug Cutting开发出这个用以创建分布式计算机环境的开源软...... 1: ...

  5. 每一位想有所成就的程序员都必须知道的15件事(走不一样的路,要去做,实践实践再实践,推销自己,关注市场)good

    从 为之漫笔作者:为之漫笔 有超过 100 人喜欢此条目 原文地址:How to advance your career? Read the Passionate Programmer! 我刚看完Ch ...

  6. 每个新手程序员都必须知道的Python技巧

    当下,Python 比以往的任何时候都更加流行,人们每天都在实践着 Python 是多么的强大且易用. 我从事 Python 编程已经有几年时间了,但是最近6个月才是全职的.下面列举的这些事情,是我最 ...

  7. 万能的林萧说:我来告诉你,一个草根程序员如何进入BAT。

    引言 首先声明,不要再问LZ谁是林萧,林萧就是某著名程序员小说的主角名字. 写这篇文章的目的其实很简单,算是对之前LZ一篇文章的补充和完善. 之前LZ写过一篇<回答阿里社招面试如何准备,顺便谈谈 ...

  8. 一个.net程序员的安卓之旅-Eclipse设置代码智能提示功能

    一个.net程序员的安卓之旅-代码智能提示功能 过完年回来就决心开始学安卓开发,就网上买了个内存条加在笔记本上(因为笔记本原来2G内存太卡了,装了vs2010.SQL Server 2008.orac ...

  9. 一个.net程序员教你使用less

    我是一个.net 程序员,虽然说一直做后台,但是web 前端也会去学,虽然说技术只是层窗户纸,但是像我这种多动症患者,不捅破我心难受啊! 好!废话不多提,下面直接正题,至于less 是什么这里不多讲因 ...

  10. 【Mood-10】每个程序员都应该读的30本书

    “如果能时光倒流,回到过去,作为一个开发人员,你可以告诉自己在职业生涯初期应该读一本,你会选择哪本书呢?我希望这个书单列表内容丰富,可以涵盖很多东西.” 很多程序员响应,他们在推荐时也写下自己的评语. ...

随机推荐

  1. JavaSE 常见时间日期

    java.util包提供了Date类来封装当前的⽇期和时间 构造函数 //当前时间 Date() //从1970年1⽉1⽇起的毫秒数作为参数 Date(long millisec) 常见方法 //返回 ...

  2. yb课堂之单机和分布式应用的登陆校验解决方案 《七》

    单机tomcat应用登陆校验 session保存在浏览器和应用服务器会话之间 用户登陆成功,服务端会保存一个session,当然客户端有一个sessionId 客户端会把sessionId保存在coo ...

  3. CM3调试系统简析

    CM3 调试系统简析 **"一直以来,单片机的调试一直不是很突出的主题,很多简单些的程序在开发中,甚至都没有调试的概念,而只是把生成的映像直接烧入片子,再根据错误症状来判断问题,然后修改程序 ...

  4. 0. 什么是C++

    什么是C++ 是C语言的扩展,有如下的两个特性: 关注性能 与底层硬件紧密结合 对象生命周期精确控制 零成本抽象(Zero-overhead Abstraction) 引入大量利于工程实践的特性 三种 ...

  5. 数据仓库建模工具之一——Hive学习第一天

    hive分布式搭建文档 谷歌浏览器下载网址:Google Chrome – Download the fast, secure browser from Google 华为云镜像站:https://m ...

  6. odoo 开发入门教程系列-一个新应用

    一个新应用 房地产广告模块 假设需要开发一个房地产模块,该模块覆盖未包含在标准模块集中特定业务领域. 以下为包含一些广告的主列表视图 form视图顶层区域概括了房产的重要信息,比如name,Prope ...

  7. Figma 替代品 Excalidraw 安装和使用教程

    如今远程办公盛行,一个好用的在线白板工具对于团队协作至关重要.然而,市面上的大多数白板应用要么功能单一,要么操作复杂,难以满足用户的多样化需求.尤其是在进行头脑风暴.流程设计或产品原型绘制时,我们常常 ...

  8. LOTO示波器功率分析功能

    LOTO示波器软件在非标功能中增加了功率分析功能,对当前屏幕的电压波形和电流波形进行了瞬时功率,视在功率以及有功功率/平均功率的分析计算. 有功功率是指电器所消耗的电能,用于产生热能.机械能或光能等, ...

  9. 【Excel】VBA编程 01 入门

    视频地址: https://www.bilibili.com/video/BV1Q5411p71p 在Excel种需要打开[开发工具]和[启用所有宏]两点 打开开发工具选项 宏启用 菜单栏才会有开发工 ...

  10. blender建模渲染Tips

    blender渲染 灯光的三种方式 1,常规灯光:shift+A选择灯光. 2,世界环境光:右侧地球图标调整. 3,物体自发光:把渲染物体变成一个发光体来进行调节灯光. 渲染视窗的调节 ctrl+b裁 ...