在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. Linux on Power 上的调试工具和技术

     Linux on Power 上的调试工具和技术 简介: 调试是一项主要的软件开发活动,作为应用程序开发人员,您无法避免对程序进行调试.有效的调试不仅能缩短软件开发周期,而且可以节省成本.本文简要介 ...

  2. TCP/IP学习(四)TCP缓冲区大小及限制(转)

    链接来自:http://blog.csdn.net/ysu108/article/details/7764461 这个问题在前面有的部分已经涉及,这里在重新总结下.主要参考UNIX网络编程. (1)数 ...

  3. 网络流(最小费用最大流):POJ 2135 Farm Tour

    Farm Tour Time Limit: 1000ms Memory Limit: 65536KB This problem will be judged on PKU. Original ID: ...

  4. 类似与fiddler的抓包工具 burp suite free edition

    burp suite free edition

  5. Uncle Sam 山姆大叔

    山姆大叔被用来代指“美国”或“美国政府”,主要在美国.英国,尤其是在新闻界中使用较多.“山姆大叔”是美国的绰号,它同自由女神一样,为世人所熟知. 形象 美国的报纸杂志.文学作品和漫画中,经常可以看到“ ...

  6. Postman interceptor

    安装 下载地址: Postman Interceptor Chrome插件下载 1. 下载的是一个crx文件. 2. 在谷歌中打开: chrome://extensions/ 3. 拖动cfx文件到 ...

  7. Angular过滤器 自定义及使用方法

    首先设置自定义过滤器. 定义模块名:angular .module('myApp') .filter('filterName',function(){ return function(要过滤的对象,参 ...

  8. today's learning of english 2

    1..fumbling    缓慢 笨拙的 2.dancing with impatience in the chekout line sighed exasperation        在安全线外 ...

  9. [Angular 2] Adding a data model

    Instead of add todo as a string, we create a data model: export class TodoModel{ constructor( public ...

  10. VS项目属性配置实验过程

    (原创,转载注明出处:http://www.cnblogs.com/binxindoudou/p/4017975.html ) 一.实验背景 cocos2d-x已经发展的相对完善了,从项目的创建.编译 ...