C++11智能指针的深度理解
平时习惯使用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智能指针的深度理解的更多相关文章
- 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), ...
- C++11——智能指针
1. 介绍 一般一个程序在内存中可以大体划分为三部分——静态内存(局部的static对象.类static数据成员以及所有定义在函数或者类之外的变量).栈内存(保存和定义在函数或者类内部的变量)和动态内 ...
- C++11智能指针之std::unique_ptr
C++11智能指针之std::unique_ptr uniqut_ptr是一种对资源具有排他性拥有权的智能指针,即一个对象资源只能同时被一个unique_ptr指向. 一.初始化方式 通过new云 ...
- 【C++11新特性】 C++11智能指针之weak_ptr
如题,我们今天要讲的是C++11引入的三种智能指针中的最后一个:weak_ptr.在学习weak_ptr之前最好对shared_ptr有所了解.如果你还不知道shared_ptr是何物,可以看看我的另 ...
- 详解C++11智能指针
前言 C++里面的四个智能指针: auto_ptr, unique_ptr,shared_ptr, weak_ptr 其中后三个是C++11支持,并且第一个已经被C++11弃用. C++11智能指针介 ...
- C++笔记(11) 智能指针
1. 设计思想 智能指针是行为类似于指针的类对象,但这种对象还有其他功能.首先,看下面的函数: void remodel(std::string & str) { std::string * ...
- C++11 智能指针
C++ 11标准库引入了几种智能指针 unique_ptr shared_ptr weak_ptr C++内存管理机制是当一个变量或对象从作用域过期的时候就会从内存中将他干掉.但是如果变量只是一个指针 ...
- C++11智能指针
今晚跟同学谈了一下智能指针,突然想要看一下C++11的智能指针的实现,因此下了这篇博文. 以下代码出自于VS2012 <memory> template<class _Ty> ...
- C++11智能指针 share_ptr,unique_ptr,weak_ptr用法
0x01 智能指针简介 所谓智能指针(smart pointer)就是智能/自动化的管理指针所指向的动态资源的释放.它是存储指向动态分配(堆)对象指针的类,用于生存期控制,能够确保自动正确的销毁动 ...
随机推荐
- SDOI2017 BZOJ 4820 硬币游戏 解题报告
写在前面 此题网上存在大量题解,但本人太菜了,看了不下10篇均未看懂,只好自己冷静分析了.本文将严格详细地论述算法(避免一切意会和玄学),因此可能会比其它题解更加理论化一些,希望能对像我一样看了其它题 ...
- loj553 「LibreOJ Round #8」MINIM
最简单的暴力dp就是f[i][j]表示到i异或和为j的最小花费. 然后我们发现两堆大小为i,j的石子合并,可以更新到一堆大小为k=i,j最高公共的1以下都是1,以上是i|j,权值为v1+v2的石子. ...
- 【强连通分量】Bzoj1051 HAOI2006 受欢迎的牛
Description 每一头牛的愿望就是变成一头最受欢迎的牛.现在有N头牛,给你M对整数(A,B),表示牛A认为牛B受欢迎. 这种关系是具有传递性的,如果A认为B受欢迎,B认为C受欢迎,那么牛A也认 ...
- centos7中输入ifconfig出现ens33,没有eth0
vmware安装的centos7中没有出现eth0网卡,也没有ip,不能上网,输入ifconfig后如下图 解决办法 1.编辑网卡的配置文件 vi /etc/sysconfig/network-scr ...
- css中固定宽高div与不固定宽高div垂直居中的处理办法
固定高宽div垂直居中 如上图,固定高宽的很简单,写法如下: position: absolute; left: 50%; top: 50%; width:200px; height:100px; m ...
- 大白话5分钟带你走进人工智能-第四节最大似然推导mse损失函数(深度解析最小二乘来源)(2)
第四节 最大似然推导mse损失函数(深度解析最小二乘来源)(2) 上一节我们说了极大似然的思想以及似然函数的意义,了解了要使模型最好的参数值就要使似然函数最大,同时损失函数(最小二乘)最小,留下了一 ...
- Actor模型-Akka
英文原文链接,译文链接,原文作者:Arun Manivannan ,译者:有孚 写过多线程的人都不会否认,多线程应用的维护是件多么困难和痛苦的事.我说的是维护,这是因为开始的时候还很简单,一旦你看到性 ...
- 干货|一个案例学会Spring Security 中使用 JWT
在前后端分离的项目中,登录策略也有不少,不过 JWT 算是目前比较流行的一种解决方案了,本文就和大家来分享一下如何将 Spring Security 和 JWT 结合在一起使用,进而实现前后端分离时的 ...
- spring源码 — 五、事务
spring提供了可配置.易扩展的事务处理框架,本文主要从一下几个方面说明spring事务的原理 基本概念 事务配置解析 事务处理过程 基本概念 事务隔离级别 在同时进行多个事务的时候,可能会出现脏读 ...
- Netty源码—六、tiny、small内存分配
tiny内存分配 tiny内存分配流程: 如果申请的是tiny类型,会先从tiny缓存中尝试分配,如果缓存分配成功则返回 否则从tinySubpagePools中尝试分配 如果上面没有分配成功则使用a ...