平时习惯使用cocos2d-x的Ref内存模式,回过头来在控制台项目中觉得c++的智能指针有点生疏,于是便重温一下。
首先有请c++智能指针们登场: std::auto_ptr、std::unique_ptr、std::shared_ptr 、std::weak_ptr


auto_ptr(已废弃的指针)

没有智能指针的c++时代,对堆内存的管理就是简单的new delete。
但是缺点是容易忘了delete释放,即使是资深码农,也可能会在某一个地方忘记delete它,造成内存泄漏。
在实际工程中,我们往往更希望把精力放在应用层上,而不是费尽心思在语言的细枝末节(内存的释放)。
于是就有了这个最原始的智能指针。

内部大概实现:
做成一个auto_ptr类,包含原始指针成员。
当auto_ptr类型的对象被释放时,利用析构函数,将拥有的原始指针delete掉。

//大概长这个样子(化简版)
template<class T>
class auto_ptr{
  T* ptr;
};

示例用法:

void runGame(){
std::auto_ptr<Monster> monster1(new Monster());//monster1 指向 一个怪物
monster1->doSomething();//怪物做某种事
}
//runGame函数执行完时,monster1被释放,然后它的析构函数也把指向的一个怪物释放了,要死带着一起死(o_o)

复制auto_ptr对象时,把指针指传给复制出来的对象,原有对象的指针成员随后重置为nullptr。
这说明auto_ptr是独占性的,不允许多个auto_ptr指向同一个资源。

void runGame(){
std::auto_ptr<Monster> monster1(new Monster());//monster1 指向 一个怪物
monster1->doSomething();//怪物做某种事
std::auto_ptr<Monster> monster2 = monster1;//转移指针
monster2->doSomething();//怪物做某种事
monster1->doSomething();//Oops!monster1智能指针指向了nullptr,运行期崩溃。
}

注意:
虽然本文简单介绍了auto_ptr。
但是不要用auto_ptr! 不要用auto_ptr!

虽然它是c++11以前的最原始的智能指针,但是在c++11中已经被弃用(使用的话会被警告)了。
它的替代品,也就是c++11新智能指针unique_ptr,shared_ptr,weak_ptr将在下文出现


unique_ptr(一种强引用指针)

“它是我的所有物,你们都不能碰它!”——鲁迅
正如它的名字,独占 是它最大的特点。

内部大概实现:

它其实算是auto_ptr的翻版(都是独占资源的指针,内部实现也基本差不多).

但是unique_ptr的名字能更好的体现它的语义,而且在语法上比auto_ptr更安全(尝试复制unique_ptr时会编译期出错,而auto_ptr能通过编译期从而在运行期埋下出错的隐患)

假如你真的需要转移所有权(独占权),那么你就需要用std::move(std::unique_ptr对象)语法,尽管转移所有权后 还是有可能出现原有指针调用(调用就崩溃)的情况。
但是这个语法能强调你是在转移所有权,让你清晰的知道自己在做什么,从而不乱调用原有指针。

示例用法:

void runGame(){
  std::unique_ptr<Monster> monster1(new Monster());//monster1 指向 一个怪物
  std::unique_ptr<Monster> monster2 = monster1;//Error!编译期出错,不允许复制指针指向同一个资源。
  std::unique_ptr<Monster> monster3 = std::move(monster1);//转移所有权给monster3.
  monster1->doSomething();//Oops!monster1指向nullptr,运行期崩溃
}
 

(额外:boost库的boost::scoped_ptr也是一个独占性智能指针,但是它不允许转移所有权,从始而终都只对一个资源负责,它更安全谨慎,但是应用的范围也更狭窄。)


shared_ptr(一种强引用指针)

“它是我们(shared_ptr)的,也是你们(weak_ptr)的,但实质还是我们的”——鲁迅
共享对象所有权是件快乐的事情。

多个shared_ptr指向同一处资源,当所有shared_ptr都全部释放时,该处资源才释放。
(有某个对象的所有权(访问权,生命控制权) 即是 强引用,所以shared_ptr是一种强引用型指针)

内部大概实现:
每个shared_ptr都占指针的两倍空间,一个装着原始指针,一个装着计数区域(SharedPtrControlBlock)的指针

(用原始指针构造时,会new一个SharedPtrControlBlock出来作为计数存放的地方,然后用指针指向它,计数加减都通过SharedPtrControlBlock指针间接操作。)

//shared计数放在这个结构体里面,实际上结构体里还应该有另一个weak计数。下文介绍weak_ptr时会解释。
struct SharedPtrControlBlock{
  int shared_count;
};
//大概长这个样子(化简版)
template<class T>
class shared_ptr{
  T* ptr;
  SharedPtrControlBlock* count;
};

每次复制,多一个共享同处资源的shared_ptr时,计数+1。每次释放shared_ptr时,计数-1。
当shared计数为0时,则证明所有指向同一处资源的shared_ptr们全都释放了,则随即释放该资源(哦,还会释放new出来的SharedPtrControlBlock)。
这也是常说的引用计数技术(好绕口)

示例用法:

void runGame(){
  std::shared_ptr<Monster> monster1(new Monster());   //计数加到1
  do{std::shared_ptr<Monster> monster2 = monster1;    //计数加到2
  }while();          
  //该栈退出后,计数减为1,monster1指向的堆对象仍存在
  std::shared_ptr<Monster> monster3 = monster1;      //计数加到2
}
//该栈退出后,shared_ptr都释放了,计数减为0,它们指向的堆对象也能跟着释放.

 

缺陷:模型循环依赖(互相引用或环引用)时,计数会不正常

假如有这么一个怪物模型,它有2个亲人关系

class Monster{
  std::shared_ptr<Monster> m_father;
  std::shared_ptr<Monster> m_son;
public:
  void setFather(std::shared_ptr<Monster>& father);//实现细节懒得写了
  void setSon(std::shared_ptr<Monster>& son);    //懒
  ~Monster(){std::cout << "A monster die!";}    //析构时发出死亡的悲鸣
};

然后执行下面函数

void runGame(){
std::shared_ptr<Monster> father = new Monster();
std::shared_ptr<Monster> son = new Monster();
father->setSon(son);
son->setFather(father);
}

猜猜执行完runGame()函数后,这对怪物父子能正确释放(发出死亡的悲鸣)吗?
答案是不能。

那么我们来模拟一遍(自行脑海模拟一遍最好),函数退出时栈的shared_ptr对象陆续释放后的情形:
开始:
father,son指向的堆对象 shared计数都是为2

son智能指针退出栈:
son指向的堆对象 计数减为1,father指向的堆对象 计数仍为2。

father智能指针退出栈:
father指向的堆对象 计数减为1 , son指向的堆对象 计数仍为1。

函数结束:所有计数都没有变0,也就是说中途没有释放任何堆对象。

为了解决这一缺陷的存在,弱引用指针weak_ptr的出现很有必要。


weak_ptr(一种弱引用指针)

“它是我们(weak_ptr)的,也是你们(shared_ptr)的,但实质还是你们的”——鲁迅

weak_ptr是为了辅助shared_ptr的存在,它只提供了对管理对象的一个访问手段,同时也可以实时动态地知道指向的对象是否存活。

(只有某个对象的访问权,而没有它的生命控制权 即是 弱引用,所以weak_ptr是一种弱引用型指针)

内部大概实现:

计数区域(SharedPtrControlBlock)结构体引进新的int变量weak_count,来作为弱引用计数。
每个weak_ptr都占指针的两倍空间,一个装着原始指针,一个装着计数区域的指针(和shared_ptr一样的成员)。
weak_ptr可以由一个shared_ptr或者另一个weak_ptr构造。
weak_ptr的构造和析构不会引起shared_count的增加或减少,只会引起weak_count的增加或减少。

被管理资源的释放只取决于shared计数,当shared计数为0,才会释放被管理资源,
也就是说weak_ptr不控制资源的生命周期。

但是计数区域的释放却取决于shared计数和weak计数,当两者均为0时,才会释放计数区域。

//shared引用计数和weak引用计数
//之前的计数区域实际最终应该长这个样子
struct SharedPtrControlBlock{
  int shared_count;
  int weak_count;
};
//大概长这个样子(化简版)
template<class T>
class weak_ptr{
  T* ptr;
  SharedPtrControlBlock* count;
};

针对空悬指针问题

空悬指针问题是指:无法知道指针指向的堆内存是否已经释放。

得益于引入的weak_count,weak_ptr指针可以使计数区域的生命周期受weak_ptr控制,

从而能使weak_ptr获取 被管理资源的shared计数,从而判断被管理对象是否已被释放。(可以实时动态地知道指向的对象是否被释放,从而有效解决空悬指针问题)

它的成员函数expired()就是判断指向的对象是否存活。

针对循环引用问题

class Monster{
  //尽管父子可以互相访问,但是彼此都是独立的个体,无论是谁都不应该拥有另一个人的所有权。
  std::weak_ptr<Monster> m_father;    //所以都把shared_ptr换成了weak_ptr
  std::weak_ptr<Monster> m_son;      //同上
public:
  void setFather(std::shared_ptr<Monster>& father); //实现细节懒得写了
  void setSon(std::shared_ptr<Monster>& son);    //懒
  ~Monster(){std::cout << "A monster die!";}     //析构时发出死亡的悲鸣
};

然后执行下面的函数

void runGame(){
  std::shared_ptr<Monster> father(new Monster());
  std::shared_ptr<Monster> son(new Monster());
  father->setSon(son);
  son->setFather(father);
}

那么我们再来模拟一遍,函数退出时栈的shared_ptr对象陆续释放后的情形:
一开始:
father指向的堆对象 shared计数为1,weak计数为1
son指向的堆对象 shared计数为1,weak计数为1

son智能指针退出栈:
son指向的堆对象 shared计数减为0,weak计数为1,释放son的堆对象,发出第一个死亡的悲鸣
father指向的堆对象 shared计数为1,weak计数减为0;

father智能指针退出栈:
father指向的堆对象 shared计数减为0,weak计数为0;释放father的堆对象和father的计数区域,发出第二个死亡的悲鸣。
son指向的堆对象 shared计数为0,weak计数减为0;释放son的计数区域。

函数结束,释放行为正确。

(可以说,当生命控制权没有彼此互相掌握时,才能正确解决循环引用问题,而弱引用的使用可以使生命控制权互相掌握的情况消失)

此外:
weak_ptr没有重载 * 和 -> ,所以并不能直接使用资源。但可以使用lock()获得一个可用的shared_ptr对象,
如果对象已经死了,lock()会失败,返回一个空的shared_ptr。

void runGame(){
  std::shared_ptr<Monster> monster1(new Monster());
  std::weak_ptr<Monster> r_monster1 = monster1;
  r_monster1->doSomething();//Error! 编译器出错!weak_ptr没有重载* 和 -> ,无法直接当指针用
  std::shared_ptr<Monster> s_monster1 = r_monster1.lock();//OK!可以通过weak_ptr的lock方法获得shared_ptr。
}

 

总结(语义)

1、不要使用std::auto_ptr(已经在C++11或以上标准中弃用)

2、当你需要一个独占资源所有权(访问权+生命控制权)的指针,且不允许任何外界访问,使用std::unique_ptr

3、当你需要一个共享资源所有权(访问权+生命控制权)的指针,使用std::shared_ptr

4、当你需要一个能访问资源,但不控制其生命周期的指针,使用std::weak_ptr

 

推荐用法:
一个shared_ptr和n个weak_ptr搭配使用而不是n个shared_ptr。
因为一般模型中,最好总是被一个指针控制生命周期,然后可以被n个指针控制访问。

逻辑上,大部分模型的生命在直观上总是受某一样东西直接控制而不是多样东西共同控制。
程序上,能够完全避免生命周期互相控制引发的 循环引用问题。

C++11智能指针的深度理解的更多相关文章

  1. c++11 智能指针 unique_ptr、shared_ptr与weak_ptr

    c++11 智能指针 unique_ptr.shared_ptr与weak_ptr C++11中有unique_ptr.shared_ptr与weak_ptr等智能指针(smart pointer), ...

  2. C++11——智能指针

    1. 介绍 一般一个程序在内存中可以大体划分为三部分——静态内存(局部的static对象.类static数据成员以及所有定义在函数或者类之外的变量).栈内存(保存和定义在函数或者类内部的变量)和动态内 ...

  3. C++11智能指针之std::unique_ptr

    C++11智能指针之std::unique_ptr   uniqut_ptr是一种对资源具有排他性拥有权的智能指针,即一个对象资源只能同时被一个unique_ptr指向. 一.初始化方式 通过new云 ...

  4. 【C++11新特性】 C++11智能指针之weak_ptr

    如题,我们今天要讲的是C++11引入的三种智能指针中的最后一个:weak_ptr.在学习weak_ptr之前最好对shared_ptr有所了解.如果你还不知道shared_ptr是何物,可以看看我的另 ...

  5. 详解C++11智能指针

    前言 C++里面的四个智能指针: auto_ptr, unique_ptr,shared_ptr, weak_ptr 其中后三个是C++11支持,并且第一个已经被C++11弃用. C++11智能指针介 ...

  6. C++笔记(11) 智能指针

    1. 设计思想 智能指针是行为类似于指针的类对象,但这种对象还有其他功能.首先,看下面的函数: void remodel(std::string & str) { std::string * ...

  7. C++11 智能指针

    C++ 11标准库引入了几种智能指针 unique_ptr shared_ptr weak_ptr C++内存管理机制是当一个变量或对象从作用域过期的时候就会从内存中将他干掉.但是如果变量只是一个指针 ...

  8. C++11智能指针

    今晚跟同学谈了一下智能指针,突然想要看一下C++11的智能指针的实现,因此下了这篇博文. 以下代码出自于VS2012 <memory> template<class _Ty> ...

  9. C++11智能指针 share_ptr,unique_ptr,weak_ptr用法

    0x01  智能指针简介  所谓智能指针(smart pointer)就是智能/自动化的管理指针所指向的动态资源的释放.它是存储指向动态分配(堆)对象指针的类,用于生存期控制,能够确保自动正确的销毁动 ...

随机推荐

  1. bzoj 1485 [HNOI2009]有趣的数列 卡特兰数

    把排好序的序列看成一对对括号,要把他们往原数列里塞,所以就是括号序合法方案数 即为卡特兰数 f(n)=Cn2nn+1 求的时候为避免除法,可以O(n)计算每个素数出现次数,最后乘起来,打完之后发现其实 ...

  2. BZOJ_1030_[JSOI2007]文本生成器_AC自动机+DP

    BZOJ_1030_[JSOI2007]文本生成器_AC自动机+DP Description JSOI交给队员ZYX一个任务,编制一个称之为“文本生成器”的电脑软件:该软件的使用者是一些低幼人群, 他 ...

  3. BZOJ_2595_[Wc2008]游览计划_斯坦纳树

    BZOJ_2595_[Wc2008]游览计划_斯坦纳树 题意: 分析: 斯坦纳树裸题,有几个需要注意的地方 给出矩阵,不用自己建图,但枚举子集转移时会算两遍,需要减去当前点的权值 方案记录比较麻烦,两 ...

  4. H5 新特性之 fileReader 实现本地图片视频资源的预览

    大家好 !!  又见面了, 今天我们来搞一搞   H5的新增API    FileReader     真是一个超级超级方便的API呢!!!很多场景都可以使用.......... 我们先不赘述MDN文 ...

  5. [转载] Java中枚举类型的使用 - enum

    目录 1 枚举类的编译特性 2 向枚举类中添加方法 3 接口内部创建枚举 4 枚举类中使用枚举 5 扩展: 验证values()不是通过父类继承的 本文转载自博客 - Java枚举类型, 博主对原文内 ...

  6. HTTP/2 简介

    支撑现有 Web 服务的 HTTP 协议距离其发布时的 1997 年已经有些年月了,随后的 HTTP/1.1 版本发布自 1999 年.随着技术的进步和需求的进化,对于数据快速高效地传输,HTTP/1 ...

  7. java基础( 九)-----深入分析Java的序列化与反序列化

    序列化是一种对象持久化的手段.普遍应用在网络传输.RMI等场景中.本文通过分析ArrayList的序列化来介绍Java序列化的相关内容.主要涉及到以下几个问题: 怎么实现Java的序列化 为什么实现了 ...

  8. 关于ef+codefirst+mysql/dapper(dbFirse)(入门)

    ef+mssql详细是许多.net程序员的标配.作为一个程序员当然不能只会mssql这一个数据库,今天简单聊聊ef+mysql.推荐新人阅读. 1]首先创建一个mvc项目,如图: 创建完毕之后再nug ...

  9. 同一容器中a标签比较多的情况下通过title属性值隐藏

    同一容器中a标签比较多的情况下如何通过title属性值控制a标签的隐藏或显示 最近项目中遇到一个IE兼容性问题,网站需要在底部footer添加"站长统计"代码,容器中动态添加很多a ...

  10. Odoo:全球第一免费开源ERP 人力资源模块操作指南(完美珍藏版)

    概述 人力资源管理概述 一般企业里,和人力资源相关的工作有:1)员工合同管理,即员工基本档案管理:2)招聘管理,即岗位及岗位人员补充管理:3)员工薪资计算: 4)员工考勤:5)员工休假管理:6)员工绩 ...