在C++的编程过程中,我们经常需要申请一块动态内存,然后当用完以后将其释放。通常而言,我们的代码是这样的:
  1: void func()
  2: {
  3: //allocate a dynamic memory
  4: int *ptr = new int;
  5:
  6: //use ptr
  7:
  8: //release allocated memory
  9: delete ptr;
  10: ptr = NULL;
  11: }
  如果这个函数func()逻辑比较简单,问题不大,但是当中间的代码有可能抛出异常时,上面的代码就会产生内存泄露(memory leak),如下面代码中第11行和12行将不会被执行。当然有码友会说用try-catch包起来就可以了,对,没错,但是代码中到处的try-catch也挺被人诟病的:
  1: void func()
  2: {
  3: //allocate a dynamic memory
  4: int *ptr = new int;
  5:
  6: throw “error”; //just an example
  7:
  8: //use ptr
  9:
  10: //release allocated memory
  11: delete ptr;
  12: ptr = NULL;
  13: }
  而且当函数有多个返回路径时,需要在每个return前都要调用delete去释放资源,代码也会变的不优雅了。
  1: void func()
  2: {
  3: //allocate a dynamic memory
  4: int *ptr = new int;
  5:
  6: if (...)
  7: {
  8: //...a
  9:
  10: //release allocated memory
  11: delete ptr;
  12: ptr = NULL;
  13: return;
  14: } else if (....)
  15: {
  16: //...b
  17:
  18: //release allocated memory
  19: delete ptr;
  20: ptr = NULL;
  21: return;
  22: }
  23:
  24: //use ptr
  25:
  26: //release allocated memory
  27: delete ptr;
  28: ptr = NULL;
  29: }
  鉴于此,我们就要想办法利用C++的一些语言特性,在函数退栈时能够将局部申请的动态内存自动释放掉。熟悉C++的码友们都知道,当一个对象退出其定义的作用域时,会自动调用它的析构函数。也就是说如果我们在函数内定义一个局部对象,在函数返回前,甚至有异常产生时,这个局部对象的析构函数都会自动调用。如果我们能够将释放资源的代码交付给这个对象的析构函数,我们就可以实现资源的自动回收。这类技术,通常被称为RAII (初始化中获取资源)。
  什么是RAII以及几个例子
  在C++等面向对象语言中,为了管理局部资源的分配以及释放(resource allocation and deallocation),实现异常安全(exception-safe)、避免内存泄露等问题,C++之父Bjarne Stroustrup发明了一种叫做”初始化中获取资源“ (RAII, Resource Acquisition Is Initialization,也可以叫做Scope-Bound Resource Management)的技术。简单来说,它的目的就是利用一个局部对象,在这个对象的构造函数内分配资源,然后在其析构函数内释放资源。这样,当这个局部对象退出作用域时,它所对应的的资源即可自动释放。在实现上,它通常有三个特点:
  创建一个特殊类,在其构造函数初申请资源; www.tygj123.com
  封装目标对象,将申请资源的目标对象作为这个特殊类的成员变量;
  在这个类的析构函数内,释放资源。
  一个典型的例子就是标准库中提供的模板类std::auto_ptr。如在《C++程序设计语言》(《The C++ Programming Language, Special Edition》, Bjarne Stroustrup著,裘宗燕译)中第327页所描述的。
  1: template
  2: class std::auto_ptr {
  3:
  4: public:
  5: //在构造函数中,获得目标指针的管理权
  6: explicit auto_ptr(X *p = 0) throw() { ptr = p; }
  7: //在析构函数中,释放目标指针
  8: ~auto_ptr() throw() { delete ptr; }
  9:
  10: //...
  11:
  12: //重装*和->运算符,使auto_ptr对象像目标指针ptr一样使用
  13: X& operator*() const throw() { return *ptr; }
  14: X* operator->() const throw() { return ptr; }
  15:
  16: //放弃对目标指针的管理权
  17: X* release() throw() { X* t = ptr; ptr = 0; return t; }
  18:
  19: private:
  20: X *ptr;
  21: };
  想要使用它,非常简单,例如
  1: #include
  2:
  3: void func()
  4: {
  5: std::auto_ptr p(new int);
  6:
  7: //use p just like ptr
  8:
  9: return;
  10: }
  另一个例子,是利用GCC中的cleanup attribute。它可以指定一个函数,在该变量退出作用域时可以执行。例如Wikipedia上提到的宏
  1: #define RAII_VARIABLE(vartype,varname,initval,dtor) \
  2: void _dtor_ ## varname (vartype * v) { dtor(*v); } \
  3: vartype varname __attribute__((cleanup(_dtor_ ## varname))) = (initval)
  我们可以这样使用,例如
  1: void example_usage() {
  2: RAII_VARIABLE(FILE*, logfile, fopen("logfile.txt", "w+"), fclose);
  3: fputs("hello logfile!", logfile);
  4: }
  还有一个例子,是在刘未鹏的博客文章”C++11 (及现代C++风格)和快速迭代式开发“中的”资源管理“一节中看到的,他借助C++11的std::function实现了这一特性。感兴趣的码友可以到他博客内阅读。
  笔者采用的方法
  对于new/delete,使用上面提到的std::auto_ptr就可以了,但是对于new/delete[]一个动态的一维数组,甚至二维数组,auto_ptr就无能为力了。而且在一些项目中,特别是一些有着悠久历史的代码中,还存在着使用malloc, new混用的现象。所以笔者设计了一个auto_free_ptr类,实现目标资源的自动回收。它的实现比较简单,只利用了RAII的第三个特点——”在类的析构函数内释放资源”,但有一个优点是可以在申请堆内存代码前使用 www.yztrans.com
  代码如下,
  1: //auto_free_ptr is only used for automation free memory
  2: template
  3: class auto_free_ptr
  4: {
  5: public:
  6: typedef enum {invalid, new_one, new_array, alloc_mem} EFLAG;
  7: auto_free_ptr() { initialize(); }
  8: ~auto_free_ptr(){ free_ptr(); }
  9:
  10: ///set the pointer needed to automatically free
  11: inline void set_ptr(T** new_ptr_address, EFLAG new_eflag)
  12: { free_ptr(); p_ptr = new_ptr_address; eflag = new_eflag; }
  13:
  14: ///give up auto free memory
  15: inline void give_up() { initialize(); }
  16:
  17: protected:
  18: inline void initialize() { p_ptr = NULL; eflag = invalid; }
  19: inline void free_ptr() throw()
  20: {
  21: if(!p_ptr || !(*p_ptr)) return;
  22:
  23: switch(eflag)
  24: {
  25: case alloc_mem: { free(*p_ptr), (*p_ptr) = NULL, p_ptr = NULL; break; }
  26: case new_one: { delete (*p_ptr), (*p_ptr) = NULL, p_ptr = NULL; break; }
  27: case new_array: { delete[] (*p_ptr),(*p_ptr) = NULL, p_ptr = NULL; break; }
  28: }
  29: }
  30:
  31: protected:
  32: T** p_ptr; //!< pointer to the address of the set pointer needed to automatically free
  33: EFLAG eflag; //!< the type of allocation
  34:
  35: private:
  36: DISABLE_COPY_AND_ASSIGN(auto_free_ptr);
  37: };
  为了使用方便,封装两个宏:
  1: // auto-free macros are mainly used to free the allocated memory by some local variables in the internal of function-body
  2: #define AUTO_FREE_ENABLE( class, ptrName, ptrType ) \
  3: auto_free_ptr auto_free_##ptrName; \
  4: auto_free_##ptrName.set_ptr(&ptrName,auto_free_ptr::ptrType)
  5:
  6: #define AUTO_FREE_DISABLE( ptrName ) auto_free_##ptrName.give_up()
  使用起来很简单,例如
  1: void func(int nLftCnt, int nRhtCnt)
  2: {
  3: if (!nLftCnt && !nRhtCnt)
  4: return;
  5:
  6: unsigned *pLftHashs = NULL;
  7: unsigned *pRhtHashs = NULL;
  8:
  9: //在申请堆内存之前,使用auto_free_ptr
  10: AUTO_FREE_ENABLE(unsigned, pLftHashs, new_array);
  11: AUTO_FREE_ENABLE(unsigned, pRhtHashs, new_array);
  12:
  13: //....
  14:
  15: if (nLftCnt)
  16: {
  17: pLftHashs = new unsigned[nLftCnt];
  18: //...a
  19: }
  20:
  21: if (nRhtCnt)
  22: {
  23: pRhtHashs = new unsigned[nRhtCnt];
  24: //...b
  25: }
  26:
  27: //....
  28:
  29: if (...)
  30: {
  31: //因为下面这个函数可以释放资源,所以在它前面放弃对目标指针的管理权
  32: AUTO_FREE_DISABLE(pLftHashs);
  33: AUTO_FREE_DISABLE(pRhtHashs);
  34:
  35: //这个函数可以释放资源
  36: free_hash_arrays(pLftHashs, pRhtHashs);
  37: }
  38: }
  同样的,有时我们需要申请一个动态二维数组,所以也实现一个对应的auto_free_2D_ptr
  1: //auto_free_2D_ptr is only used for automation free memory of 2D array
  2: template
  3: class auto_free_2D_ptr
  4: {
  5: public:
  6: typedef enum {invalid, new_one, new_array, alloc_mem} EFLAG;
  7: auto_free_2D_ptr() { initialize(); }
  8: ~auto_free_2D_ptr() { free_ptr(); }
  9:
  10: ///set the pointer needed to automatically free
  11: inline void set_ptr( T** new_ptr_address,EFLAG new_eflag, int new_length_row )
  12: { free_ptr(); p_ptr = new_ptr_address; eflag = new_eflag; length_row = new_length_row; }
  13:
  14: //give up auto free memory
  15: inline void give_up() { initialize(); }
  16:
  17: protected:
  18: inline void initialize() { p_ptr = NULL; eflag = invalid; length_row = 0;}
  19: inline void free_ptr() throw()
  20: {
  21: if(!p_ptr || !(*p_ptr)) return;
  22:
  23: for(int i = 0; i < length_row; i++)
  24: {
  25: if(!(*p_ptr)[i]) continue;
  26: switch(eflag)
  27: {
  28: case alloc_mem: { free((*p_ptr)[i]); break; }
  29: case new_one: { delete (*p_ptr)[i]; break; }
  30: case new_array: { delete[] (*p_ptr)[i]; break; }
  31: }
  32: (*p_ptr)[i] = NULL;
  33: }
  34: switch(eflag)
  35: {
  36: case alloc_mem: { free((*p_ptr)); break; }
  37: default: { delete[] (*p_ptr); break; }
  38: }
  39: (*p_ptr) = NULL, p_ptr = NULL;
  40: }
  41:
  42: protected:
  43: T** p_ptr; //!< pointer to the address of the set pointer needed to automatically free
  44: EFLAG eflag; //!< the type of allocation
  45: int length_row; //!< the row length such as ptr[length_row][length_col]
  46:
  47: private:
  48: DISABLE_COPY_AND_ASSIGN(auto_free_2D_ptr);
  49: };
  50:
  51: #define AUTO_FREE_2D_ENABLE( class, ptrName, ptrType, rowNum ) \
  52: auto_free_2D_ptr auto_free_##ptrName; \
  53: auto_free_##ptrName.set_ptr(&ptrName,auto_free_2D_ptr::ptrType, rowNum)
  54:
  55: #define AUTO_FREE_2D_DISABLE( ptrName ) AUTO_FREE_DISABLE( ptrName )
  下面是个例子
  1: void func(int row, int col)
  2: {
  3: if (!row && !col)
  4: return;
  5:
  6: int **ptr = new int*[ row ];
  7: for( int r = 0; r < row; ++r ) { ptr[r] = new int[ col ];}
  8:
  9: AUTO_FREE_2D_ENABLE( int, ptr, new_array, row );
  10:
  11: //....
  12: }
  到这里就结束了,有些码友可能会说,何必这么麻烦,boost内有很多智能指针供选择,用share_ptr, scoped_ptr, scoped_array,unique_ptr, auto_ptr 中的一个不就行了吗? 没错!如果你正在开发的代码中,允许用boost,并且在相关程序接口统一都用智能指针来管理、不会用到源对象指针的话,当然优先选boost,但是当你的代码中由于历史原因,有些接口不可变更,且new/delete, malloc/free都存在,而且依然需要使用源对象指针来完成大部分工作时,不妨试试我设计的这个阉割版的scoped_ptr/scoped_array。总之,根据自己的实际情况来选择合适的方案,如果标准方案不适用,就自己写一个。

利用C++ RAII技术自动回收堆内存的更多相关文章

  1. 【原创】利用C++ RAII技术自动回收堆内存

    [说明]这篇文章本来发布在我个人网站的博客上,但由于:1,打算以cnblogs为家了:2. 关于智能指针部分需要修订,所有将修订版发在这里,作为第一篇文章. 常遇到的动态内存回收问题 在C++的编程过 ...

  2. Java堆内存是线程共享的!面试官:你确定吗?

    Java作为一种面向对象的,跨平台语言,其对象.内存等一直是比较难的知识点,所以,即使是一个Java的初学者,也一定或多或少的对JVM有一些了解.可以说,关于JVM的相关知识,基本是每个Java开发者 ...

  3. 利用jmap和MAT等工具查看JVM运行时堆内存

    jmap JDK自带了一些工具可以帮助我们查看JVM运行的堆内存情况,常用的是jmap命令 jmap -heap <pid> 打印堆的使用情况 那么,从这个输出中我们也可以大致看出堆的结构 ...

  4. java中栈内存与堆内存(JVM内存模型)

    java中栈内存与堆内存(JVM内存模型) Java中堆内存和栈内存详解1 和 Java中堆内存和栈内存详解2 都粗略讲解了栈内存和堆内存的区别,以及代码中哪些变量存储在堆中.哪些存储在栈中.内存中的 ...

  5. 栈 堆 stack heap 堆内存 栈内存 内存分配中的堆和栈 掌握堆内存的权柄就是返回的指针 栈是面向线程的而堆是面向进程的。 new/delete and malloc/ free 指针与内存模型

    小结: 1.栈内存 为什么快? Due to this nature, the process of storing and retrieving data from the stack is ver ...

  6. Linux堆内存管理深入分析(下)

     Linux堆内存管理深入分析 (下半部) 作者@走位,阿里聚安全 0 前言回顾 在上一篇文章中(链接见文章底部),详细介绍了堆内存管理中涉及到的基本概念以及相互关系,同时也着重介绍了堆中chunk分 ...

  7. Linux堆内存管理深入分析(上)

    Linux堆内存管理深入分析(上半部) 作者:走位@阿里聚安全   0 前言 近年来,漏洞挖掘越来越火,各种漏洞挖掘.利用的分析文章层出不穷.从大方向来看,主要有基于栈溢出的漏洞利用和基于堆溢出的漏洞 ...

  8. Linux堆内存管理深入分析

    (上半部) 作者:走位@阿里聚安全 前言 近年来,漏洞挖掘越来越火,各种漏洞挖掘.利用的分析文章层出不穷.从大方向来看,主要有基于栈溢出的漏洞利用和基于堆溢出的漏洞利用两种.国内关于栈溢出的资料相对较 ...

  9. JVM堆内存监测的一种方式,性能调优依旧任重道远

    上月,由极客邦.InfoQ和听云联合主办2016 APMCon中国应用性能管理大会圆满落下帷幕.会上,Java冠军Martijn Verburg进行了一场Java and the Machine的分享 ...

随机推荐

  1. AlgorithmsI Exercises: UnionFind

    Question1 Give the id[] array that results from the following sequence of 6 unionoperations on a set ...

  2. POJ1416 Shredding Company(dfs)

    题目链接. 分析: 这题从早上调到现在.也不算太麻烦,细节吧. 每个数字都只有两种状态,加入前一序列和不加入前一序列.DFS枚举. #include <iostream> #include ...

  3. 【数学】CSU 1810 Reverse (2016湖南省第十二届大学生计算机程序设计竞赛)

    题目链接: http://acm.csu.edu.cn/OnlineJudge/problem.php?id=1810 题目大意: 一个长度为N的十进制数,R(i,j)表示将第i位到第j位翻转过来后的 ...

  4. Tomcat死机报OutOfMemoryError: PermGen space错误

    最近,用户没怎么使用系统,页面就卡死,访问不了.仔细一看是Tomcat假死,好几次都这样.重启也慢的很,很着急.最后,看了下 conf/logs 里的配置文件,发现是 OutOfMemoryError ...

  5. osg

    智能指针使用: osg::Geode* geode=new osg::Geode;//新建Geode指针 osg::ref_ptr<osg::Geode>geodePtr=geode;// ...

  6. 数学概念——A 几何概型

    You are going from Dhaka to Chittagong by train and you came to know one of your old friends is goin ...

  7. Nodejs in Visual Studio Code 06.新建Module

    1.开始 Node.js:https://nodejs.org 2.Moudle js编程中,由于大家可以直接在全局作用域中编写代码,使开发人员可以很容易的新建一个全局变量或这全局模块,这些全局变量或 ...

  8. CentOS虚拟机不能联网状况下yum方式从本地安装软件包(转载的)

    大家都知道yum是linux下一个非常好用的软件安装/卸载软件,它方便操作,而且最厉害的是可以解决令人头疼的包依赖关系.但是若是你的linux不能联网,若想使用yum安装软件,可以依照下面的方法. 1 ...

  9. JS常用扩展

    // 清除两边的空格 String.prototype.trim = function() { return this.replace(/(^\s*)|(\s*$)/g, ''); }; // 合并多 ...

  10. 百度地图api之如何自定义标注图标

    在百度地图api中,默认的地图图标是一个红色的椭圆形.但是在项目中常常要求我们建立自己的图标,类似于我的这个 操作很简单,分如下几步进行 步骤一:先ps一个图标,大小要合适,如果要背景透明的,记得保存 ...