如果你还在使用传统的C++,那么可以肯定堆内存的管理让你头痛过!在传统的C++领域,堆内存管理上我们能借用的现成工具就只有auto_ptr。但是很不幸用auto_ptr管理堆内存简直就是个错误。auto_ptr的问题可以归结为两点:

  1. 不能配合STL容器一起使用。将auto_ptr置于容器中,就是个编译错误(如果是一个编译错误,你得感谢,还好编译期就发现了)
  2. 不能管理动态数组。auto_ptr只能管理单个对象指针,如果指针是通过new T[num]的方式生成的,那不好意思了,这个就是个埋得比较深的坑了,哪天踩到了,就只能求老天爷了
  3. 除了内存资源,其他资源无法自动管理。

既然auto_ptr这么不好,那我们还有其他的选择么?这得感谢boost、感谢tr1库。他们引入了多个新的智能指针。有了tr1库,我们就可以告别传统C++了。

tr1库中主要引入了两个智能指针。一个是shared_ptr;一个是weak_ptr。本文主要介绍下shared_ptr。

什么事shared_ptr呢?这个就不多说了,基本的概念参见这里。大致就是一个基于引用计数的智能指针。

那么shared_ptr到底有什么优势呢?首先,很显然得,它必须得解决auto_ptr不能解决的问题:

  1. 能够和STL容器配合使用。这个不多说,谁用谁知道。
  2. 能够动态管理堆内存,包括动态数组。
  3. 能管理其它类型的资源。

shared_ptr如何管理堆数组?我们先来看一下shared_ptr的一个构造函数:

template<class _Ux, class _Dx>
class shared_ptr(_Ux *_Px, _Dx _Dt);

shared_ptr之所以能管理动态数组的关键就在这个构造函数的第二参数类型_Dx。_Dx类型的对象指定了如何释放_Px指针,_Dt(_Px)。所以,我们可以定义一个仿函数来解决这个问题:

 template <typename T>
struct memory_delete<T[]>
{
void operator ()(T *ptr) const { delete[] ptr; }
};

当然了,如果你使用的编译器版本够高,你可以直接选用default_delete<T[]>来作为_Dx。

到这里,我们也可以发现,_Dx作为一个模板类型,其本质是定义了一个释放器。一个释放器意味着不管_Px是什么指针,只要有对于的释放方式,你都可以用shared_ptr来进行管理。只要将释放的逻辑写成上面的仿函数形式即可(如果编译器支持,你也可以用ambda表示直接表述,或者只用function+bind的形式)。

所以,用malloc分配的内存,我们的释放器实现时,需要调用free。如果是其他第三方类库返回的对象指针,比方说libevent的event_base_new,我们的释放器实现时,需要调用event_base_free。

所以,shared_ptr可以动态管理资源。

关于shared_ptr最基础的部分差不多就介绍完了。下面说一点应用,我们从线程的角度来入手。

线程对于每一个程序员都不陌生。线程在使用上比较让人恼火的一件事情就是对象的跨线程使用。要保证对象的跨线程使用,要么你的对象是一个全局对象;要么你这个对象就是个堆上的对象,通过指针在多个线程中使用。后面这种方法绝对是常用的手段之一。而这种手段恰恰又是特别地恼火。怎么说呢?

在服务端开发中,对象指针的多线程使用最难搞清楚的情况是,我怎么知道我现在用的这个指针所指向的对象还活着?为什么这么说呢。资源有分配就会有释放,当某个线程执行到释放的逻辑时,这个线程根本无法知道它释放完后,其他线程是否持有这个对象指针;同时,持有这个对象指针的线程也没有什么有效的方法能够知道其他某个线程正在是否这个指针指向的对象。

  • 使用if?开玩笑么?if只是判断指针空不空,它哪知道指向的那个内存是否有释放呢?所以,插一句话就是,在raw pointer上应用if根本毫无意义。if测试后发现指针非空,但是程序还是挂了,让人费解。
  • 还有第二种方法么?给指针一个标志位?朦胧感觉,好像可以。如果某个线程把这个指针干掉了,设置下标志位,其他线程使用这个指针时,先判断下这个标志位。

所以,解决这个问题的本质就是,当某个线程把共享指针干掉后,必须能想办法通知到其他使用该对象的线程。

要解决raw pointer的这个问题,我们必须得引入一个间接层,或者说一个代理。这个代理的生命周期必须长于这个raw pointer。多线程访问这个raw pointer,必须通过这个代理来进行。包括释放这个对象也必须通过这个代理来进行。既然如此,那shared_ptr就是我们的理想选择之一了。

又因为shared_ptr在构造的时候就能够指定如果析构这个对象,所以当对象不被任何线程使用时,这个对象就会自动被析构,并且是正确地被析构。你完全不用担心使用的这个对象是否已经被析构,只要有人在用,它就是活着的。这里,唯一需要注意的就是,shared_ptr很可能延长了对象的生命周期。如果这个不是问题,那么这个解决方案就没有问题。

事实上这个释放器的威力还远不止这些。我们知道,如果一个资源是通过某个DLL中的方法生成的,那么这个资源的释放函数必须也要由这个DLL显示提供,并通过该释放函数释放资源。一旦忘了这条准则,当我们的DLL跟新后,就很有可能遇到莫名其妙的运行时问题。而shared_ptr的释放器能很好得帮我们解决这个问,只要将这个shared_ptr对象从DLL中返回出来就可以了,资源的释放在DLL内部构造shared_ptr对象时就指定好。那么就万事OK了。是不是很方便(当然,前提条件还是有的,就是shared_ptr的二进制必须兼容)?

shared_ptr如此强大,那么在使用上还有没有其他要注意的点?

首先,我们要知道,shared_ptr是引用计数型智能指针。引用计数要考虑的一个大问题就是循环引用。简单地描述这个问题就是,你有一个管理类,管理了一波指针,他们会被跨线程使用,所以,你把他们声明为shared_ptr。这些指针对象内部同时也有一个指向管理类对象的指针。因为管理类也会被多线程使用,所以你把这个指针也设计成shared_ptr。OK,你循环引用了,这些对象都不会自动销毁了。要解决这个问题,你需要恰当得使用weak_ptr。怎么用,前面的那个链接已经给出了基本的原则。

shared_ptr:资源管理利器的更多相关文章

  1. Android资源管理利器Resources和AssetManager

    前言  : Android工程在运行的时候往往需要引用资源.使用 Resources 来获取 res 目录下的各种与设备相关的资源.而使用 AssetManager 来获取 assets 目录下的资源 ...

  2. Android实现apk插件方式换肤

    换肤思路: 1.什么时候换肤? xml加载前换肤,如果xml加载后换肤,用户将会看见换肤之前的色彩,用户体验不好. 2.皮肤是什么? 皮肤就是apk,是一个资源包,包含了颜色.图片等. 3.什么样的控 ...

  3. RAII惯用法:C++资源管理的利器(转)

    RAII惯用法:C++资源管理的利器 RAII是指C++语言中的一个惯用法(idiom),它是“Resource Acquisition Is Initialization”的首字母缩写.中文可将其翻 ...

  4. [.net 面向对象程序设计进阶] (27) 团队开发利器(六)分布式版本控制系统Git——在Visual Studio 2015中使用Git

    [.net 面向对象程序设计进阶] (26) 团队开发利器(六)分布式版本控制系统Git——在Visual Studio 2015中使用Git 本篇导读: 接上两篇,继续Git之旅 分布式版本控制系统 ...

  5. [.net 面向对象程序设计进阶] (24) 团队开发利器(三)使用SVN多分支并行开发(下)

    [.net 面向对象程序设计进阶] (24) 团队开发利器(三)使用SVN多分支并行开发(下) 本篇导读: 接上篇继续介绍SVN的高级功能,即使用分支并行开发.随着需求的不断变更,新功能的增加.特别是 ...

  6. [.net 面向对象程序设计进阶] (23) 团队开发利器(二)优秀的版本控制工具SVN(上)

    [.net 面向对象程序设计进阶] (23) 团队开发利器(二)优秀的版本控制工具SVN(上) 本篇导读: 上篇介绍了常用的代码管理工具VSS,看了一下评论,很多同学深恶痛绝,有的甚至因为公司使用VS ...

  7. shared_ptr 和 unique_ptr

    c++11标准废除乐auto_ptr, C++ 标准库智能指针 使用这些智能指针作为将指针封装为纯旧 C++ 对象 (POCO) 的首选项. unique_ptr 只允许基础指针的一个所有者. 除非你 ...

  8. Effective C++笔记:资源管理

    资源:动态分配的内存.文件描述器.互斥锁.图形界面中的字型与笔刷.数据库连接以及网络sockets等,无论哪一种资源,重要的是,当你不再使用它时,必须将它还给系统. 条款13:以对象管理资源 当我们向 ...

  9. auto_ptr,shared_ptr 智能指针的使用

    Q: 那个auto_ptr是什么东东啊?为什么没有auto_array?A: 哦,auto_ptr是一个很简单的资源封装类,是在<memory>头文件中定义的.它使用“资源分配即初始化”技 ...

随机推荐

  1. 基础笔记12(socket,url网络通信)

    进一步深入socket 1.网络通信条件: .IP地址,可用主机名. .传输数据时将不用的应用程序通过数字标识区分开来,这种标识称为逻辑端口,也称端口.(0-65535端口,一般系统预留0-1024) ...

  2. js加密解密

    <script> document.write("<xmp>"); document.write(function(p,a,c,k,e,r){}(''.sp ...

  3. [Java] SoapUI使用Java获取各时间日期方法

    import java.util.*; import java.text.SimpleDateFormat; // current date String dateNew = today() // t ...

  4. Easy Sysprep更新日志-skyfree大神

    Easy Sysprep更新日志: Skyfree 发表于 2016-1-22 13:55:55 https://www.itsk.com/forum.php?mod=viewthread&t ...

  5. loadrunner json

    Action(){ web_custom_request("JRPT_WriteLog", //VuGen中树形视图中显示的名称 "Url=url", //请求 ...

  6. ExtJS自制表格Grid分页条

    试过Grid自带的load和分页功能,没有成功,干脆就自己写了...... 主要是查询条件比较复杂...... 希望哪位大神能有更好的意见. Ext.define('MyApp.ux.Paginati ...

  7. python——赋值与深浅拷贝

    初学编程的小伙伴都会对于深浅拷贝的用法有些疑问,今天我们就结合python变量存储的特性从内存的角度来谈一谈赋值和深浅拷贝~~~ 预备知识一——python的变量及其存储 在详细的了解python中赋 ...

  8. 重写TextField Rect 改变显示位置

    很简单很常用的一些东西,希望给需要的人帮助. 效果图如下: 自定义textField init() { super.init(frame: CGRect(x: , y: , width: yourWi ...

  9. sql server 维护计划与作业关系区别

    sql server 维护计划与作业关系区别 对于二者的区别,你可以把维护计划看作是针对数据库进行维护的作业模板.自定义作业具有更广泛的用途,当然,也具有更复杂的操作.所以,如果 仅仅是做个数据库优化 ...

  10. 给libpcap增加一个新的捕包方法

    libpcap是一个网络数据包捕获函数库,功能非常强大,提供了系统独立的用户级别网络数据包捕获接口,Libpcap可以在绝大多数类unix 平台下工作.大多数网络监控软件都以它为基础,著名的tcpdu ...