用RAII技术管理资源及其泛型实现


前言

RAII的含义是“资源获取即初始化”。

一段看似安全的代码

首先看一段代码:

  1. try{
  2. int *p = new int[100];
  3. // ... do something
  4. delete[] p;
  5. }catch(exception &e){
  6. // .....
  7. }

这段代码中,我们先进行了动态内存分配,使用完释放,看起来很完美,但是这段程序是否真的保证不会发生内存泄漏?

考虑这样一种情形,程序在使用这段内存的过程中throw一个异常,于是程序转向catch块,然后XXX。 这段内存被释放了吗? 显然没有。 那么这段程序应该从哪里改进呢?

对象的生命期

考虑一个问题:C++中对象的声明期是怎样的?在C++中,对象创建的方式有两种,一种是栈上,一种是堆上创建。

  1. {
  2. Animal a; // stack
  3. Animal *pa = new Animal(); // heap
  4. }

上面的代码中,第一个对象创建在栈上,更明确的说法是它是一个局部变量,这意味着它的生命期起源于被创建的这行语句,终结与所在作用域的末尾,也就是这里的右花括号(})。
而第二个对象呢?它是采用所谓的动态内存分配生成的,需要程序员手工去释放,当调用delete的时候才销毁,但调用delete的时机不是固定的。
也就是说,栈对象的生命期是明确的,而堆对象的生命期由于取决于调用delete的时机,因而是不明确的。

看到这里,之前那段有可能内存泄漏的代码如何去改进呢?

答案就是用栈对象明确的生命期去管理资源

用对象的生命期管理资源

试想一下,如果我们把之前程序中,对内存的分配写在构造函数中,把释放资源写在析构函数中,而栈对象的生命期是明确的,当该管理资源的对象过期时,连同它管理的资源一起释放,岂不是非常智能化?

我们尝试着写出下列代码:

  1. class ScopePtr{
  2. public:
  3. ScopePtr(int *p):_p(p){
  4. }
  5. ~ScopePtr(){
  6. delete[] _p;
  7. }
  8. private:
  9. int *_p;
  10. };

我们把之前的代码做如下的改进:

  1. try{
  2. ScopePtr scope(new int[100]);
  3. // ... do something
  4. }catch(exception &e){
  5. // .....
  6. }

再来分析一下这段代码:
如果正常执行,那么当执行完try块时,scope对象过期,执行析构函数,同时释放了那段数组。如果使用的过程中发生了异常,那么当程序进入catch块时,同样会销毁try内的局部变量。
无论是哪种情况,内存总是会被释放。
如果这里不是int,而是其他复杂的类型,使用这个封装的ScopePtr是不是不太方便?显然不会,我们去重载成员操作符就可以了,使它表现的像个指针,这就是一个最简单的智能指针的产生。
问题得到了完美的解决!

资源获取即初始化

我们上面解决问题的办法就是RAII技术,RAII的含义是“资源获取即初始化”,这个概念有两个要点:

  • 获得资源后立即放进管理对象
  • 管理对象运用析构函数确保资源被释放

看另外一个例子:我们在访问一些临界区资源的时候通常需要加锁,所以产生了下面的代码:

  1. {
  2. mutex.lock();
  3. //do sth..
  4. mutex.unlock();
  5. }

这种方式是很容易出现问题的,例如程序中间遇见错误情况需要退出这个函数,此时很容易忘记解锁:

  1. {
  2. mutex.lock();
  3. //do sth..
  4. if(...){
  5. return false // forgot to unlock
  6. }
  7. // ...
  8. mutex.unlock();
  9. }

此时如果再次进行Lock操作,就造成了死锁。
解决这个问题的办法仍然很简单,我们去写一个类:

  1. class MutexLockGuard{
  2. public:
  3. MutexLockGuard(MutexLock mutex):_mutex(mutex){
  4. _mutex.lock();
  5. }
  6. ~MutexLockGuard(){
  7. _mutex.unlock();
  8. }
  9. private:
  10. MutexLock &_mutex;
  11. };

这样刚才那段代码就可以修改成:

  1. {
  2. MutexLockGuard guard(lock);
  3. //do sth..
  4. if(...){
  5. return false
  6. }
  7. // ...
  8. }

这样,一旦离开这段代码,程序立刻自动解锁。
不过为了防止错误使用这个类,例如:

  1. MutexLockGuard(lock);

可以定义一个宏:

  1. #define MutexLockGuard(m) "ERR MutexLockGuard"

这样我们在错误使用的时候,编译期间就能发现错误。

一种泛型解决方案

刘未鹏在他的《C++11(及现代C++风格)和快速迭代式开发》中提出了一种泛型实现,利用了C++11的function和Lambda匿名函数,如下:

  1. class ScopeGuard
  2. {
  3. public:
  4. explicit ScopeGuard(std::function<void()> onExitScope)
  5. : onExitScope_(onExitScope), dismissed_(false)
  6. { }
  7. ~ScopeGuard()
  8. {
  9. if(!dismissed_)
  10. {
  11. onExitScope_();
  12. }
  13. }
  14. void Dismiss()
  15. {
  16. dismissed_ = true;
  17. }
  18. private:
  19. std::function<void()> onExitScope_;
  20. bool dismissed_;
  21. private: // noncopyable
  22. ScopeGuard(ScopeGuard const&);
  23. ScopeGuard& operator=(ScopeGuard const&);
  24. };

使用方式也很简单:

  1. HANDLE h = CreateFile(...);
  2. ScopeGuard onExit([&] { CloseHandle(h); });

其实就是将该资源释放的函数代码段注册到Scope类,其中原理不再赘述。

与其他语言的对比

RAII是C++独有的编程手段。通过RAII技术我们能够做到资源不需要使用时立即释放,这是其他GC语言所不具备的。
以Java为例,Java具有完善的GC(Garbage Collection,垃圾回收)机制,但是存在如下的缺点:

  • GC只能回收内存,而对于打开的文件、数据库连接等仍然需要手工关闭。
  • GC因为进程优先级等原因,回收效率底下,详情可以参考孟岩的《垃圾收集机制(Garbage Collection)批判》

conclusion

RAII技术是现代C++编程技术中及其重要的一部分,甚至有人称其为“C++编程中最重要的编程技法”,可见其重要性。通过RAII,我们完全可以实现资源的自动化管理,写出永不内存泄漏的程序。

参考资料

  • 《C++ Primer》
  • 《Effective C++》
  • 《Linux多线程服务器端编程》

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/352053.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

使用Lambda在AWS云上使用Java

如今&#xff0c;Amazon Web Services越来越受欢迎。 Java是AWS的一等公民&#xff0c;它很容易上手。 部署应用程序有些不同&#xff0c;但是仍然很容易和方便。 AWS Lambda是一种计算服务&#xff0c;您可以在其中将代码上传到AWS Lambda&#xff0c;并且该服务可以使用AWS…

Lintcode--3(366)--斐波那契数列

题目&#xff1a;查找斐波纳契数列中第 N 个数。所谓的斐波纳契数列是指&#xff1a;前2个数是 0 和 1 。第 i 个数是第 i-1 个数和第i-2 个数的和。斐波纳契数列的前10个数字是&#xff1a;0,1,1,2,3,5,8,13,21... 程序&#xff1a; class Solution { public: /* * para…

nt6启动菜单自动修复工具_轻量级windows系统修复,清理工具——Dism++

收藏分享计划读完需要4分钟速读仅需 2 分钟Dism是一款操作简单&#xff0c;轻量级的系统维护工具。Dism 作为第三版清理工具更加深入系统底层&#xff0c;功能和清理效果都非常不错1 简介Dism 是由初雨团队采用微软内部 API 编写的一款开源免费的实用工具&#xff0c;最开始的名…

【日常小记】linux中强大且常用命令:find、grep

在linux下面工作&#xff0c;有些命令能够大大提高效率。本文就向大家介绍find、grep命令&#xff0c;他哥俩可以算是必会的linux命令&#xff0c;我几乎每天都要用到他们。本文结构如下&#xff1a; find命令 find命令的一般形式 find命令的常用选项及实例 find与xargs grep命…

Spring----最小化Spring配置

在Spring的配置文件中&#xff0c;我们可以使用<bean>元素定义Bean,以及使用<constructor-arg>或着<property>元素装配bean,这对于包含少量Bean的应用来说以经非常不错了&#xff0c;但是随着应用的发展&#xff0c;我们不得不编写越来越复杂的XML配置。为解…

Lintcode--2(56)--两数之和

题目&#xff1a;给一个整数数组&#xff0c;找到两个数使得他们的和等于一个给定的数 target。 你需要实现的函数twoSum需要返回这两个数的下标, 并且第一个下标小于第二个下标。注意这里下标的范围是 0 到 n-1。注意事项你可以假设只有一组答案。样例给出 numbers [2, 7, 11…

qml如何发布程序_首创PC端小程序直播发布会,360如何与手机厂商一起共振?

文 | Toby Lu全新的线上发布会形式&#xff0c;正在搅动着手机品牌营销江湖。疫情之下&#xff0c;线上发布会的形式成为手机品牌产品亮相的最佳形式&#xff0c;与传统的联合各家媒体做直播不同&#xff0c;聚焦于一个媒体平台&#xff0c;全场景、全链路的营销模式&#xff0…

CUBA Platform 6.3的新增功能

我们很自豪地宣布新版本的CUBA平台和Studio全面上市&#xff01; 也许这是有史以来功能最丰富的平台版本之一–在各个级别都有重要的变化&#xff1a;体系结构&#xff0c;可扩展性&#xff0c;API可用性和性能。 本文介绍了该平台的主要增强功能。 发行说明中提供了完整的更…

Python字符串的编码与解码(encode与decode)

首先要搞清楚&#xff0c;字符串在Python内部的表示是unicode编码&#xff0c;因此&#xff0c;在做编码转换时&#xff0c;通常需要以unicode作为中间编码&#xff0c;即先将其他编码的字符串解码&#xff08;decode&#xff09;成unicode&#xff0c;再从unicode编码&#xf…

Linux复习笔记

常用命令&#xff1a; pwd &#xff1b;cd&#xff1b; ls&#xff1b; cp&#xff1b; mv&#xff1b; rm &#xff1b;cat&#xff1b; stat&#xff1b; 01234567---r---w-rw---xr-x-wxrwx目录&#xff1a; root: 在root下: useradd mjq 创建mjq用户 passwd mjq 创建密…

Lintcode--4(1)--A+B

题目&#xff1a;给出两个整数a和b, 求他们的和, 但不能使用 等数学运算符。 说明&#xff1a; a和b都是 32位 整数么&#xff1f;是的我可以使用位运算符么&#xff1f;当然可以 样例&#xff1a;如果 a1 并且 b2&#xff0c;返回3显然你可以直接 return a b&#xff0c;但…

1w存银行一年多少利息_100万存银行一年利息多少?能赚多少钱?

100万存银行一年利息多少&#xff0c;是否可以辞掉工作什么都不做随着经济水平的提升&#xff0c;大家手上的存款也越来越多了&#xff0c;众所周知&#xff0c;将资金存放在银行是可以赚取利息收益的&#xff0c;那么如果我们有100万的存款资金后&#xff0c;一年可以获得多少…

Notepad++背景颜色设置

经常试用notepad看代码&#xff0c;白色的背景连续看的时间长了眼睛很容变花&#xff0c;所以找了相关的设置选项&#xff0c;分享给大家 具体设置步骤如下&#xff1a; 然后如下设置 这样前景色背景色已经发生改变了哟&#xff0c;下面再修改下选中行的背景色吧&#xff0c;你…

思维水题

题目链接&#xff1a;https://codeforces.com/problemset/problem/1082/A 题目意思&#xff1a; 一本书有1到n页&#xff0c;起始页数是x&#xff0c; 一次翻页只能翻d&#xff08;向前或者向后翻&#xff0c;但是不能翻出去&#xff0c;例如n5,x1 y5,d10 &#xff09; …

常用的位运算

1、按位与 & 0 & 0 0&#xff1b;0 & 1 0&#xff1b;1 & 0 0&#xff1b;1& 1 1 同时为1则结果为1&#xff0c;否则为0&#xff1b; 如3 & 8 3 00000011 5 00000101 结果为 00000001 2、按位或 || 0|00; 0|11; 1|01; 1|11; 两…

atom feed_适用于Atom Feed的Spring MVC

atom feed如何仅使用两个类就将提要&#xff08;Atom&#xff09;添加到Web应用程序&#xff1f; Spring MVC呢&#xff1f; 这是我的假设&#xff1a; 您正在使用Spring框架 您有一些要发布在供稿中的实体&#xff0c;例如“新闻” 您的“新闻”实体具有creationDate&#…

argb可以和rgb同步吗_神光同步酷炫幻彩,安钛克光棱120 RGB风扇套装

目前这个时代&#xff0c;电脑的各种配件来说&#xff0c;走向了万物皆要有光的风格。相信发光耳机&#xff0c;发光键盘&#xff0c;发光鼠标大家也都见过不少。侧透机箱甚至全透机箱搭配各类酷炫光效在电竞游戏领域也变得越来越火。许多小伙伴在组装一款电脑时也会选择发光风…

使用GDB命令行调试器调试C/C++程序

编译自&#xff1a;http://xmodulo.com/gdb-command-line-debugger.html 作者&#xff1a; Adrien Brochard 原创&#xff1a;LCTT https://linux.cn/article-4302-1.html 译者&#xff1a; SPccman 本文地址&#xff1a;https://linux.cn/article-4302-1.html 没有调试器的…

制造业物料清单BOM、智能文档阅读、科学文献影响因子、Celebrated Italian mathematician ZepartzatT Gozinto 与 高津托图...

意大利数学家Z.高津托 意大利伟大数学家Sire Zepartzatt Gozinto的生卒年代是一个谜[1]&#xff0c;但是他发明的 “高筋图” 在 制造资源管理、物料清单&#xff08;BOM&#xff09;管理、智能阅读、科学文献影响因子计算 等方面具有重要应用。 高津托图 下图是一个制造业物料…

Dools的DMN运行时示例

正如去年宣布的那样 &#xff0c;Drools 7.0将在合规性级别3上为DMN模型提供完整的运行时支持。 在撰写本文时&#xff0c;运行时实现功能已经完成 &#xff0c;并且团队现在正在努力进行改进&#xff0c;以进行错误修复和用户友好。 不幸的是&#xff0c;对于7.0版本&#x…