目录

介绍

RAII技术在锁上的应用

互斥锁

RAII技术在指针上的应用

智能指针


介绍

RAII :Resource Acquisition Is Initialisation (RAII)

(任何资源的获取都应该发生在类的构造函数中,资源的释放应发生在析构函数中,即资源的生命周期与对象绑定)

RAII是一种使用在面向对象语言中的资源(内存,互斥锁,或者文件描述符)管理机制,使用RAII的语言中,最出名的当属C++和RUST。对C++来说,许多公司已经开始禁用裸指针(强制使用基于RAII的智能指针)来避免内存泄漏。而RUST,正是因为强制RAII机制使得其拥有了绝对的内存安全。

RAII技术在锁上的应用

互斥锁

std::mutex mut;int write_to_a_file_descriptor(std::string content)
{mut.lock();// critical area below (might throw exception)// Writing  content to a file descriptor...// Critical areas abovemut.unlock();
}

以上代码展示了一个将字符串写进某个文件描述符的函数,并且这个函数会被很多线程并行调用 (这种情况在高并发线上服务的logger中非常常见),因此这个共用的文件描述符必须用一个互斥锁保护起来,否则不同线程的字符串会混在一起。这段代码看起来仿佛没有问题,但是如果当写IO时是抛出了异常,call stack会被直接释放,也就意味着 unlock方法不会执行,造成永久的死锁。这个问题可以像java一样用一个try-catch语句来避免但是也会让代码变得臃肿和难看。并且在复杂的逻辑中,往往很可能会忘了解锁,或者花很多精力来管理锁的获得和释放(如果在一个函数调用中有多处返回,每个return statement之前都需要 unlock)。

这就是RAII发挥其威力的时候了,下面一段代码将展示如何用 lock_guard来使我们的代码异常安全并且整洁。

std::mutex mut;int write_to_a_file_descriptor(std::string content)
{std::lock_guard<std::mutex> lock(mut);// critical area below (might throw exception)// Writing  content to a file descriptor...// Critical areas above
}

lock_guard保证在函数返回之后释放互斥锁,因此使得开发人员不需要为抛出异常的情况担心且不需手动释放锁。但是 lock_guard是如何做到的呢?笔者将尝试自己手动实现一个 lock_guard

template <typename T>
class lock_guard
{
private:T _mutex;
public:explicit lock_guard(T &mutex) : _mutex(mutex){_mutex.lock();}~lock_guard(){_mutex.unlock();}
};

从以上实现中可看出, lock_guard在构造函数中锁住了引用传入的mutex (resource acquisition is initialisation),并且在析构函数中释放锁。

RAII技术实现自动化解锁

一般人写的加锁代码:

{mutex_.lock();//XXXif(....)return;//XXXmutex_.unlock();
}

显然,这段代码如果函数if成立就会造成忘记解锁,或者中途异常,走不到 mutex_.unlock();也会造成一直锁住。

那么如何防止这种情况,我们采用和智能指针相同的策略:对象生命周期结束自动释放资源 -----RAII

class MutexLockGuard : NonCopyable
{
public:MutexLockGuard(MutexLock &mutex) : mutex_(mutex){mutex_.lock();}~MutexLockGuard(){mutex_.unlock();}
private:MutexLock &mutex_;
};

这个类对于我们编写优雅的代码,好处是显而易见的,例如:

size_t Buffer::size() const
{mutex_.lock();int ret = queue_.size();mutex_.unlock();return queue_.size();
}

这段代码实在称不上美观,但是有了MutexLockGuard,我们可以写出:

size_t Buffer::size() const
{MutexLockGuard lock(mutex_);return queue_.size();
}

代码的美观性提高了许多。

当然,有一种使用方式是错误的,例如:

size_t Buffer::size() const
{MutexLockGuard(mutex_);return queue_.size();
}

这段代码的加锁周期仅限于那一行,为了防止错误使用,我们增加一个宏:

#define MutexLockGuard(m) "Error MutexLockGuard"

这样当错误使用的时候,会导致编译错误,使得我们早些发现问题。

RAII技术在指针上的应用(资源管理)

智能指针

接下来笔者将介绍RAII在C++中最强的应用:智能指针。

C++中一个非常常见的应用场景就是调用一个函数来产生一个对象,然后消费这个对象,最后手动释放指针。如以下代码所示。

class my_struct
{
public:my_struct() = default;
};template <typename T>
T* get_object()
{return new T();
}int main()
{auto obj = get_object<my_struct>();// consume the object// ...// consume finishdelete obj;
}

然而,大型应用程序中,指针的产生和消费错综复杂,忘记释放指针,或者读取已经释放的指针,就是C/C++各种内存泄漏的万恶之源。

而自从C++11推出智能指针后,其极大地减轻了C++开发者们内存管理的压力。再也不用通过手动 delete来释放内存。

下面的代码将展示如何用 std::unique_ptr来管理指针。

using namespace std;
class my_struct
{
public:my_struct() = default;
};template <typename T>
unique_ptr<T> get_object()
{return unique_ptr<T>(new T());
}int main()
{auto obj = get_object<my_struct>();// consume the object// ...// consume finish
}

在上述代码中,当main函数退出时, std::unique_ptr在自己的析构函数中释放指针,而为了防止有别的 std::unique_ptr指向自己管理的对象而导致的提早释放与空指针访问std::unique_ptr禁止了 copy constructorcopy assignment。有人可能会疑惑, get_object函数创建的 unique_ptr为什么没有在函数返回前释放指针?这是因为 std::unique_ptr实现了 move constructor(一种可以将资源从另一个对象“偷”过来的构造函数)并在返回时将指针传给了main函数中 obj变量。如果不太理解发生了什么,可以看一下以下我自己尝试实现的 unique_ptr.

template <typename T>
class unique_ptr
{
private:T* _ptr;
public:// Construct from plain pointerexplicit unique_ptr(T* ptr) : _ptr(ptr) {std::cout << "unique_ptr constructed" << std::endl;};// Move constructorunique_ptr(unique_ptr &&ptr) noexcept : _ptr(ptr._ptr) {ptr._ptr = nullptr;std::cout << "unique_ptr move constructed" << std::endl;}// Copy constructor is forbiddenunique_ptr(unique_ptr &ptr) = delete;// Move assignmentunique_ptr& operator=(unique_ptr &&ptr) noexcept {if (this == &ptr) {return *this;}_ptr = ptr._ptr;ptr._ptr = nullptr;return *this;}// Copy assignment is forbiddenunique_ptr& operator=(unique_ptr &ptr) = delete;~unique_ptr() {delete _ptr;std::cout << "unique_ptr destructed" << std::endl;}T* operator->() {return _ptr; }
};

代码看上去比较复杂,不过我将一个方法一个方法地和大家分析。

  1. 第8行代码实现了最基本的构造函数:从一个裸指针开始构造。
  2. 第13行实现了 move constructor,这个方法会用一个已有的 unique_ptr来构造一个新的对象,它将旧 unique_ptr的指针替换为 nullptr来防止多个指针指向相同对象。
  3. 第19行禁止了 copy constructor的使用,因为不允许多个指针指向同一对象。
  4. 第22行实现了 move assignment,原理与 move constructor相同。
  5. 第32行禁止了 copy assignment,原理与 copy constructor相同。
  6. 第34行是析构函数,将最终释放指针。
  7. 第39行实现了 operatoroverload,使得我们可以像访问普通指针一样访问 unique_ptr

我们来用我们自己定义的 unique_ptr运行一下看会发生什么:

class my_struct
{
public:std::string _name = "name";my_struct() = default;explicit my_struct(std::string name) : _name(std::move(name)) {std::cout << "my_struct constructed" << std::endl;}~my_struct() {std::cout << "my_struct destructed" << std::endl;}
};template<typename T>
unique_ptr<T>get_object()
{return unique_ptr<T>(new T("struct name"));
}int main() {unique_ptr<my_struct> obj = get_object<my_struct>();std::cout << obj->_name << std::endl;
}
console output:my_struct constructed
unique_ptr constructed
struct name
my_struct destructed
unique_ptr destructed

首先, my_struct被构造,然后 unique_ptr被构造,并且可以发现, my_struct的析构函数会在 unique_ptr的析构函数返回前执行,这意味着我们成功地将指针的life cycle绑定到了 unique_ptr上!不过,细心的同学可能发现了,全程 unique_ptrmove constructor都没有被call过,但是我之前明确说了,main函数中的 obj是用 get_object函数中构造的 unique_ptr通过 move constructor构造的。可是为什么我们没有抓到 move constructor打印出来的东西呢?这是因为C++编译器做了一个叫做 copy elision的优化,来避免不必要的构造和析构,例如本例中,两个函数中的 unique_ptr对象其实是一个东西,因此他们之间的转换和赋值被优化掉了。如果我们通过 std::move来强制 move constructor发生,如下所示:

我们将看到这样的信息:

my_struct constructed
unique_ptr constructed
unique_ptr move constructed
unique_ptr destructed
struct name
my_struct destructed
unique_ptr destructed

此时我们可以清晰地看到,main函数中的 obj是通过 move constructor构造的,并且在其构造完成之后, get_object函数中构造的 unique_ptr对象被析构了,因为我们已经提早将其内部指针替换成了 nullptr, 其析构函数什么都不会释放。

智能指针中,除了 std::unique_ptr,还有其他类型,比如允许多个指针指向同一变量的 std::shared_ptr,其内存管理逻辑会复杂许多,如果有同学有兴趣,可以在评论中告诉我,下次专门写一篇文章讲如何实现 std::shared_ptr

技术总结

通过这篇文章,相信大家都体会到了RAII的威力,其将资源绑定到轻量级对象(比如智能指针,内存占用很少,可以像普通指针一样随意传递)的方法使得我们再也不需要关心在获取资源之后对资源的释放。

原文:https://cloud.tencent.com/developer/article/1594609

RAII技术带来的缺点

RAII不是万能的技术,在某些资源需要非常精细控制的情况下,依然需要手动管理。必须提早(对象生命周期结束前)释放的东西你就别用raii。

一定要在用完之后立即释放资源,不想等到作用域结束,那只有纯手工操作。RAII都不行。

RAII的作用是帮助处理发生异常时的资源释放问题以及避免程序员忘记释放资源。

你要说最优化,那当然是手写。但大量程序员离开GC写个内存不泄露的程序都很难,就别指望最优化了。

【RAII】RAII 技术(内存安全解决技术/自动化解锁技术)相关推荐

  1. 百度地图开发技术方案及解决办法

    技术方案及解决办法 文章目录 技术方案及解决办法 总体方案 基础框架 前端网页 后端程序 结合搭建方法 跨域解决 python处理excel 问题描述 解决办法 网页结构 vue实例 内容引入 ele ...

  2. 谈谈HMI 的自动化生成技术

    人机界面(HMI)是自动化领域不可或缺重要组成部分.尽管人机界面系统的设计看上去并没有太大的技术门槛,但是设计一个HMI系统的工作量是巨大的.如果你没有足够的耐心便完成不了一个通用的HMI系统.构建U ...

  3. Server 2012 Hyper-v新功能之二:自动化支持技术

    Server 2012 Hyper-v新功能之一:客户端 Hyper-V Windows PowerShell 是在 Windows Server 中执行自动化任务的脚本解决方案,新的适用于 Wind ...

  4. 深入Java自动化探针技术的原理和实践

    转至作者 蒋志伟:深入Java自动化探针技术的原理和实践 前言 建议阅读时间 30~40分钟 读者需要对Java JVM 一定了解,文章会系统的介绍Java 探针核心原理和技术实现,总结目前一些主流的 ...

  5. 2020五大技术趋势一览!超自动化、人类增强技术、无人驾驶发展、机器视觉崛起、区块链实用化...

    文章原载于  RichardLiu 自动驾驶技术的发展 近年来,自动驾驶技术一直在发展,特斯拉.英特尔等大公司在这一领域取得了长足的进展. 虽然我们还没有达到L4级或L5级自动驾驶汽车的水平,但我们已 ...

  6. 拳王虚拟项目公社:虚拟商品全自动化赚钱技术,虚拟产品自动赚钱能信吗?虚拟资源教程

    拳王一直都在说虚拟项目是互联网的主流项目,其低成本高价值,受到了众多互联网从业者的追捧. 很多人愿意做虚拟项目的原因,无非是因为他的价值高,成本低,复制性比较强,很容易掌握整个流程,而且售后维护也比较 ...

  7. 使用计算机对炼钢过程中,【转炉自动化炼钢技术应用分析原稿材料】

    转炉自动化炼钢技术应用分析(原稿) <转炉自动化炼钢技术应用分析(原稿).doc>由会员分享,可免费在线阅读全文,更多与<转炉自动化炼钢技术应用分析(原稿)>相关文档资源请在帮 ...

  8. 原创|干货|长文|264亿件包裹怎么破?闪电式自动化分拣技术 细节大起底!

    大家好,我是你们的老朋友,智能仓储物流技术研习社,社长,老K.上周给大家分享了仓储物流中心中的"拆零拣选"的章法和具体方案,这次老K带大家一起来进入下一个环节:自动化分拣. 由于整 ...

  9. NLP实践|CCKS2020金融知识图谱自动化构建技术方法总结

    每天给你送来NLP技术干货! 编辑:AI算法小喵 写在前面 文本是参加2020CCKS评测 基于本体的金融知识图谱自动化构建技术 之后的一篇总结博客,笔者查阅了大量文献,并做了大量采用深度学习模型的实 ...

最新文章

  1. 关于C语言中的一些注意问题的小记之一----+号使用
  2. Scala微服务架构 三
  3. 不区分大小写比较Java_java-如何使字符串比较不区分大小写?
  4. 背完这444句,你的口语绝对不成问题了
  5. 从宝马与京东携手,透视汽车后市场变革
  6. (五)操作系统安全概念和设计思想
  7. 机器学习的一些注意事项
  8. java arraycopyof_Java中System.arraycopy()和Arrays.copyOf()的区别
  9. 脱单盲盒|交友盲盒系统
  10. linux uvc stm32,linux uvc深入理解(三)
  11. uml类图例子_五分钟带你读懂UML类图
  12. Android百分比布局支持和垂直TextView
  13. 实用工具,可以生成dal层的相关cs代码
  14. 线上故障排查大体思路
  15. 【软考系统架构设计师】2020年下系统架构师综合知识历年真题
  16. webstorm做项目第三天——开始登陆界面
  17. Cornerstone 使用详解(版本2)
  18. wing ftp server网页无法访问
  19. 21个故事的启示(来源于网络)
  20. Java 密码学相关知识

热门文章

  1. 格式化代码 format.js
  2. 计算机截图方法,电脑简单又实用的截图方法推荐
  3. RDKit|分子3D构象生成与优化
  4. VSCode 使用教程--编写调试C/C++程序
  5. 盘点!网络安全厂商都有哪些?
  6. 2019年7月勒索病毒疫情分析
  7. echarts自定义X轴、Y轴间距
  8. 纯css画梯形,纯CSS3实现的梯形立方体
  9. linux du命令,du命令示例
  10. ucos II任务管理之三:删除任务