当前位置: 首页 > 编程日记 > 正文

C++智能指针: shared_ptr 实现详解

文章目录

      • shared_ptr描述
        • 声明
        • 作用
        • 原理实现
      • 函数使用
      • 关于shared_ptr循环引用问题

shared_ptr描述

声明

shared_ptr属于C++11特性中新加的一种智能指针,它的实现方式是模版类,头文件<memory>
template <class T> class shared_ptr
所以使用shared_ptr的声明方式即为
std::shared_ptr<type_id> statement 其中type_id为类型(可以是基本数据类型或者类),statement即为模版类对象

作用

shared_ptr 的理解如下:

  • 使用一种叫做RAII(Resource Acquisition Is Initialization)的技术作为实现基础:
    在对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终保持有效,最后在对象析构的时候释放资源
    raii技术的好处是:
    • 不需要显式释放资源
    • 对象所拥有的资源在其生命周期内始终有效
  • 防止忘记调用delete释放内存或者程序异常退出时没有释放内存。
  • 同时它能够将值语义转为引用语义(即shared_ptr可以让多个指针指向相同的对象,共享同一块地址空间),shared_ptr使用引用技术方式来统计当前对象被引用的次数,每一次执行析构函数,引用计数就会-1,当引用计数减为0时自动删除所指对象,回收对象空间。

原理实现

常用操作以及源码实现如下:
类声明如下

temple<typename T>
class SharedPtr {
public:... 
private:T *_ptr;int *_refCount;     //这里使用int型指针是为了保证拷贝构造时同一个地址空间的引用计数增加
};
  • constructor构造函数,初始化的时候默认引用计数为0
    SharedPtr() : _ptr((T *)0), _refCount(0)
    {}
    
    在使用普通指针初始化两个shared_ptr的时候,两个shared_ptr的引用计数都为1
    SharedPtr(T *obj) : _ptr(obj), _refCount(new int(1))
    {
    } 
    
    在进行拷贝构造的时候,使用shared_ptr去初始化另一个shared_ptr的时候引用计数会+1
    SharedPtr(SharedPtr &other) : _ptr(other._ptr), _refCount(&(++*other._refCount))
    {
    }
    
  • destructor析构函数,析构的时候在指针不为空且引用计数为0的时候释放空间
    ~SharedPtr()
    {if (_ptr && --*_refCount == 0) {delete _ptr;delete _refCount;}
    }
    
  • operator = 当使用一个shared_ptr给另一个shared_ptr赋值的时候这里需要注意
    1. 由于指针指向发生变化,原来的_ptr指针的引用计数要–,且当达到了0的时候要注意回收原来指针的空间
    2. _ptr又指向了新的_ptr,则新的_ptr指针的引用计数要++
    SharedPtr &operator=(SharedPtr &other)
    {if(this==&other)return *this;//新指针引用计数要++  ++*other._refCount;//原指针引用计数要--,如果为0,则释放空间if (--*_refCount == 0) {delete _ptr;delete _refCount;}//重新进行指向 _ptr = other._ptr;_refCount = other._refCount;return *this;
    }
    
  • operator* 解引用运算符,直接返回底层指针的引用,即共享的地址空间内容
    T &operator*()
    {if (_refCount == 0)return (T*)0;return *_ptr;
    }
    
  • operator ->指针运算符
    T *operator->()
    {if(_refCount == 0)return 0;return _ptr;
    }
    

函数使用

主要案例如下
在这里插入图片描述

  • 构造函数 constructor,std::shared_ptr初始化案例如下,以及对应的refcount打印
    #include <iostream>
    #include <memory>struct C {int* data;};int main () {std::shared_ptr<int> p1;//默认构造函数,refcount为0std::shared_ptr<int> p2 (nullptr);//使用一个空的对象初始化时refcount也为0//普通指针初始化是引用计数为1,p3,p4std::shared_ptr<int> p3 (new int);std::shared_ptr<int> p4 (new int, std::default_delete<int>());//拥有allocator的时候初始化同样引用计数为1//但是紧接着又用改智能指针拷贝构造初始化其他智能指针p6,//所以最后其引用计数为2,p5std::shared_ptr<int> p5 (new int, [](int* p){delete p;}, std::allocator<int>());//这里p6本身为1,但是因为使用std::move去初始化p7,将p6指向转给了p7//则p6智能指针recount--变为0,p7 ++由1变为2std::shared_ptr<int> p6 (p5);std::shared_ptr<int> p7 (std::move(p6));std::shared_ptr<int> p8 (std::unique_ptr<int>(new int));std::shared_ptr<C> obj (new C);std::shared_ptr<int> p9 (obj, obj->data);std::cout << "use_count:\n";std::cout << "p1: " << p1.use_count() << '\n';std::cout << "p2: " << p2.use_count() << '\n';std::cout << "p3: " << p3.use_count() << '\n';std::cout << "p4: " << p4.use_count() << '\n';std::cout << "p5: " << p5.use_count() << '\n';std::cout << "p6: " << p6.use_count() << '\n';std::cout << "p7: " << p7.use_count() << '\n';std::cout << "p8: " << p8.use_count() << '\n';std::cout << "p9: " << p9.use_count() << '\n';return 0;
    }
    
    输出如下
    use_count:
    p1: 0
    p2: 0
    p3: 1
    p4: 1
    p5: 2
    p6: 0
    p7: 2
    p8: 1
    p9: 2
    
  • 析构函数
    // shared_ptr destructor example
    #include <iostream>
    #include <memory>int main () {auto deleter = [](int*p){std::cout << "[deleter called]\n"; delete p;};//使用特殊的delete函数去构造,析构的时候会执行改delete 中lamada表达式内容.即构造函数案例中的p5初始化方式std::shared_ptr<int> foo (new int,deleter);std::cout << "use_count: " << foo.use_count() << '\n';return 0;                        // [deleter called]
    }
    
    输出如下
    use_count: 1
    [deleter called]
    
  • =赋值运算符
    // shared_ptr::operator= example
    #include <iostream>
    #include <memory>int main () {std::shared_ptr<int> foo;std::shared_ptr<int> bar (new int(10));/*此时foo的引用计数为0,bar初始化后引用计数为1这里进行赋值操作,即foo的指向发生了变化,指向了bar1.foo引用计数--,因为已经为0了,此时直接释放foo原来的空间2.bar引用计数++变为23.更改foo的引用计数和bar引用计数相等,并使得foo指向bar.因为他们共享同一个空间执行完之后fool和bar引用计数都相等,且解引用后数值都为0*/foo = bar;                          // copystd::cout << "*foo: " << *foo << " foo.count " << foo.use_count() << '\n';std::cout << "*bar: " << *bar << " bar.count " << bar.use_count() << '\n';/*这里重新对bar进行了初始化,即原先的指向发生了更改,所以它的引用计数--,并且内容变为新的地址空间内容20foo继续指向原先空间,但是内容并未变化。同时原先地址因为bar并不引用了,所以foo的引用计数--*/bar = std::make_shared<int> (20);   // movestd::cout << "*foo: " << *foo << " foo.count " << foo.use_count() << '\n';std::cout << "*bar: " << *bar << " bar.count " << bar.use_count() << '\n';std::unique_ptr<int> unique (new int(30));foo = std::move(unique);            // move from unique_ptrstd::cout << "*foo: " << *foo << " foo.count " << foo.use_count() << '\n';std::cout << "*bar: " << *bar << " bar.count " << bar.use_count() << '\n';return 0;
    }
    
    输出如下
    *foo: 10 foo.count 2
    *bar: 10 bar.count 2
    *foo: 10 foo.count 1
    *bar: 20 bar.count 1
    *foo: 30 foo.count 1
    *bar: 20 bar.count 1
    
  • shared_ptr::swap,交换两个shared_ptr地址空间内容,但并不破坏各自引用计数
    
    // shared_ptr::swap example
    #include <iostream>
    #include <memory>int main () {std::shared_ptr<int> foo (new int(10));std::shared_ptr<int> bar (new int(20));std::cout << "befor swap" << '\n';std::cout << "*foo: " << *foo << " foo.count " << foo.use_count() << '\n';std::cout << "*bar: " << *bar << " bar.count " << bar.use_count() << '\n';foo.swap(bar);std::cout << "after swap" << '\n';std::cout << "*foo: " << *foo << " foo.count " << foo.use_count() << '\n';std::cout << "*bar: " << *bar << " bar.count " << bar.use_count() << '\n';return 0;
    }
    
    输出如下
    befor swap
    *foo: 10 foo.count 1
    *bar: 20 bar.count 1
    after swap
    *foo: 20 foo.count 1
    *bar: 10 bar.count 1
    
  • shared_ptr::reset 替换所管理的对象
    // shared_ptr::reset example
    #include <iostream>
    #include <memory>int main () {std::shared_ptr<int> sp;  // emptysp.reset (new int);       // 替换所管理对象,让其更换地址指向std::cout << *sp << " " << sp.use_count() << " " << sp << '\n';*sp=10;std::cout << *sp << " " << sp.use_count() << " " << sp << '\n';sp.reset (new int);       // 清除上一个指针指向的内容,重新进行更换std::cout << *sp << " " << sp.use_count() << " " << sp << '\n';*sp=20;std::cout << *sp << " " << sp.use_count() << " " << sp << '\n';sp.reset();               // deletes managed object//std::cout << *sp << " " << sp.use_count() << '\n';return 0;
    }
    
    输出如下,可以看到reset之后的地址发生了变化,即更改了指针的指向
    0 1 0x434cd50
    10 1 0x434cd50
    0 1 0x434cd90
    20 1 0x434cd90
    
  • shared_ptr::get获取初始指针
    	// shared_ptr::get example
    #include <iostream>
    #include <memory>int main () {int* p = new int (10);std::shared_ptr<int> a (p);std::shared_ptr<int> b (new int(20));//此时a和p共享同一个地址空间,所以a和p的内容都为0,地址空间一样if (a.get()==p)std::cout << "a and p point to the same location " << a << " " << p << '\n';std::cout << *a.get() << "\n";std::cout << *a << "\n";std::cout << *p << "\n";//此时a将共享空间释放,重新更换指向b,但是*p为普通指针,并无法跟随a更换指,所以p的地址内容变为0a=b;std::cout << "a and p after copy " << a << " " << b << '\n';// three ways of accessing the same address:std::cout << *a.get() << "\n";std::cout << *a << "\n";std::cout << *p << "\n";return 0;
    }
    
    输出如下:
    a and p point to the same location 0x21cf2f0 0x21cf2f0
    10
    10
    10
    a and p after copy 0x21cf330 0x21cf330
    20
    20
    0
    

关于shared_ptr循环引用问题

循环引用是指两个shared_ptr初始化之后相互指向,在函数作用域结束之后由于两个指针都保持相互的指向,引用计数都为1,此时各自占用的内存空间无法释放,最终产生内存泄露
举例如下:

#include<iostream>  
#include<memory>  using namespace std;  class B;  
class A{  public:  shared_ptr<B> ptr_A;  ~A(){  cout << "refcount " << ptr_A.use_count() << '\n';cout<<"~A()"<<endl;  }  
};  
class B{  public:  //shared_ptr<A> ptr_B;//当采用shared_ptr指向A时会形成循环引用,则什么都不会输出说明对象没有被析构,可怕的内存泄露....  shared_ptr<A> ptr_B;//当采用弱引用时,避免了循环引用,有输出,说明对象被析构了  ~B(){  cout << "refcount " << ptr_B.use_count() << '\n';cout<<"~B()"<<endl;  }  
};  
int main(){  shared_ptr<A> a(new A);  shared_ptr<B> b(new B);  a->ptr_A=b;  b->ptr_B=a;//若是循环引用:当a、b退出作用域的时候,A对象计数不为1(b保留了个计数呢),同理B的计数也不为1,那么对象将不会被销毁,内存泄露了... cout << a.use_count() << " "  << b.use_count()<< endl;return 0;  
}  

输出如下,可以看到释放的之前两个智能指针的引用计数都为2,析构的时候各自引用计数执行–到·1,最终无法释放

2 2

将classB中的shared_ptr更改为weak_ptr即可成功释放

#include<iostream>  
#include<memory>  using namespace std;  class B;  
class A{  public:  shared_ptr<B> ptr_A;  ~A(){  cout << "refcount " << ptr_A.use_count() << '\n';cout<<"~A()"<<endl;  }  
};  
class B{  public:  //shared_ptr<A> ptr_B;//当采用shared_ptr指向A时会形成循环引用,则什么都不会输出说明对象没有被析构,可怕的内存泄露....  weak_ptr<A> ptr_B;//当采用弱引用时,避免了循环引用,有输出,说明对象被析构了  ~B(){  cout << "refcount " << ptr_B.use_count() << '\n';cout<<"~B()"<<endl;  }  
};  
int main(){  shared_ptr<A> a(new A);  shared_ptr<B> b(new B);  a->ptr_A=b;  b->ptr_B=a;//若是循环引用:当a、b退出作用域的时候,A对象计数不为1(b保留了个计数呢),同理B的计数也不为1,那么对象将不会被销毁,内存泄露了...  return 0;  
}  

输出如下,调用析构函数之前引一个智能指针的引用计已经将为1,执行析构之后即为0

1 2
refcount 1
~A()
refcount 0
~B()

参考文档:
http://www.cplusplus.com/reference/memory/shared_ptr/
https://www.xuebuyuan.com/3190713.html

相关文章:

linux文本处理常用命令

linux文本处理常用命令 linux文本处理命令&#xff1a;grep、sed、printf、awk 1.grep grep的作用是按行查找字符&#xff0c;输出包含字符的行。 #从文件查询 grep hello filename.txt #从管道的输入查询 cat filename.txt|grep hello grep使用示例&#xff1a; grep的查找主要…

arduino 控制无刷电机_智能控制轮椅来了,残疾人的福音!

传统的轮椅已被证明是非常宝贵的工具&#xff0c;为残疾人提供了很多便利&#xff0c;但其只能限制坐在一个位置。外国的一个研究团队通过开发一个功能强大的多功能轮椅&#xff0c;使用户能够通过手机应用程序轻松地在各种各样的位置之间进行切换。(图片来自 IC photo)这是一种…

python科学计算整理

网站&#xff1a; http://bokeh.pydata.org/gallery.html 转载于:https://www.cnblogs.com/gogly/p/3453341.html

TP-link 841N 刷DD-WRT固件

2012年4月20号 今天&#xff0c;笔者逛“太平洋”的时候&#xff0c;发现了一个关于TP-Link 840N刷DD-WRT的帖子&#xff0c;进去逛了一会&#xff0c;突然记得自己家中的那个路由好像也是这个型号的&#xff0c;二话不说&#xff0c;果断收录这条资料&#xff0c;并把所需要的…

网格的铺设问题——骨牌

Problem Description 有一个大小是 2 x n 的网格&#xff0c;现在需要用2种规格的骨牌铺满&#xff0c;骨牌规格分别是 2 x 1 和 2 x 2&#xff0c;请计算一共有多少种铺设的方法。 Input 输入的第一行包含一个正整数T&#xff08;T<20&#xff09;&#xff0c;表示一共有 T…

C++智能指针:weak_ptr实现详解

文章目录weak_ptr描述声明作用原理实现函数成员使用总结weak_ptr描述 声明 头文件&#xff1a;<memory> 模版类&#xff1a;template <class T> class weak_ptr 声明方式&#xff1a;std::weak_ptr<type_id> statement 作用 根据boost库的官方描述&#…

在PHP中使用全局变量的几种方法

简介即使开发一个新的大型PHP程序&#xff0c;你也不可避免的要使用到全局数据&#xff0c;因为有些数据是需要用到你的代码的不同部分的。一些常见的全局数据有&#xff1a;程序设定类、数据库连接类、用户资料等等。有很多方法能够使这些数据成为全局数据&#xff0c;其中最常…

python处在哪个阶段_python 基础复习

1、简述cpu、内存、硬盘的作用cpu(1)cpu&#xff1a;处理逻辑运算、算术运算(2)cpu&#xff1a;接受指令传给电脑硬件&#xff0c;让其运行内存&#xff1a;(1)内存&#xff1a;从硬盘中读取数据&#xff0c;供其cpu调取指令运行&#xff0c;短暂的存贮数据&#xff1b;运行速度…

android用户界面之WebView教程实例汇总

一、WebView教程1.Android---UI篇---WebView&#xff08;网络视图&#xff09;http://www.apkbus.com/android-14259-1-1.html2.webview学习记录http://www.apkbus.com/android-44567-1-1.html3.Android中使用WebView, WebChromeClient和WebViewClient加载网页http://www.apkbu…

java下输出中文的一点研究

网上或者大部分书上都说Java中输出中文使用FileReader类就可以了&#xff0c;但是当你读取一个中文文档时&#xff0c;你会发现&#xff0c;除了乱码&#xff0c;还是乱码。究其原因&#xff0c;这其实是文件流读取时使用的编码方式和文件本身编码方式不同,造成读取出来文件乱码…

C++智能指针:unique_ptr详解

文章目录unique_ptr描述声明作用函数指针描述总结unique_ptr描述 声明 头文件&#xff1a;<memory> 模版类&#xff1a; 默认类型template <class T, class D default_delete<T>> class unique_ptr数组类型template <class T, class D> class uniq…

川崎机器人示教盒维修_专业维修丹阳市KUKA库卡KRC2库卡C4主板维修{苏州罗韦维修}...

发那科机器人故障分析 发那科伺服放大器上LED指示灯故障维修大全_发那科机器人维修&#xff0c;FANUC机器人保养&#xff0c;伺服电机示教器减速器维修&#xff0c;驱动器维修&#xff0c;苏州发那科机器人维修&#xff0c;本文主要介绍了发那科伺服放大器上因故障而出现的各种…

js 文本反向排列显示

一次面试遇到这样的题目 反向输出“how are you” 解决方法 <script language"JavaScript">var message1"how are you";var message2"";for (countmessage1.length; count > 0; count--)message2message1.substring(count,count-1);doc…

2012年12月4期手机网页开发

最近主要做手机上页面的开发&#xff0c;主要框架是&#xff0c;手机安装客户端&#xff0c;加载主站手机应用页面&#xff0c;手机客户端配合主站功能实现本地扫描或重力感应的效果。 针对安卓系统&#xff0c;在开发和调试时发现如下问题&#xff1a;1路径错误&#…

zabbix4.0构建实录

【Nginx】 #wget -O /etc/yum.repos.d/epel.repo http://mirrors.aliyun.com/repo/epel-7.repo [rootcentos ~]# yum -y install zlib pcre pcre-devel openssl openssl-devel[rootcentos ~]# useradd -s /sbin/nologin nginx [rootzabbix-server ~]# yum install -y nginx 【M…

ceph-kvstore-tool 工具使用详解

文章目录简介使用总结简介 ceph-kvstore-tool工具是用来获取存放在leveldb或者rocksdb数据库中的键值元数据。并且该工具能够对kvstore中的数据进行配置&#xff0c;就像是对离线后的osd操作osd map一样 使用该工具&#xff0c;需要安装ceph-test-12.2.1.06-0.el7.centos.x86_…

springboot 订单重复提交_Spring Boot (一) 校验表单重复提交

一、前言在某些情况下&#xff0c;由于网速慢&#xff0c;用户操作有误(连续点击两下提交按钮)&#xff0c;页面卡顿等原因&#xff0c;可能会出现表单数据重复提交造成数据库保存多条重复数据。存在如上问题可以交给前端解决&#xff0c;判断多长时间内不能再次点击保存按钮&a…

智能会议白板系统每日开发记录

智能会议白板系统&#xff0c;在开发过程中&#xff0c;整个项目期限内&#xff0c;每月&#xff0c;每周&#xff0c;每天要做的事情&#xff0c;作为组长的记录&#xff0c;多有不足之处&#xff0c;望指点。 转载于:https://www.cnblogs.com/mayijun/p/3458039.html

java.lang.OutOfMemoryError: PermGen space及其解决方法

PermGen space的全称是Permanent Generation space,是指内存的永久保存区域OutOfMemoryError: PermGen space从表面上看就是内存益出&#xff0c;解决方法也一定是加大内存。说说为什么会内存益出&#xff1a;这一部分用于存放Class和Meta的信息,Class在被 Load的时候被放入Per…

ceph-dencoder工具使用详解

文章目录简介使用decode命令用法encode简介 ceph-dencoder工具是一个序列化编码、解码并且打印ceph数据结构的工具。它主要用来调试和测试ceph不同版本之间的兼容性问题 该工具是由 ceph-common-12.2.1.06-0.el7.centos.x86_64 rpm包生成 本文章是根据ceph-12.2.1版本来描述改…

EBS fnd_global.apps_initialize

原型&#xff1a;fnd_global.apps_initialize(user_ID, Responsibility_id,Responsibility_application_id);作用&#xff1a;在数据库的会话中设置全局变量&#xff0c;和用户概要信息。参数获得&#xff1a;参数一&#xff0c;用户ID select user_idfrom fnd_userwhere user_…

js判断鼠标靠近屏幕最侧面的监听_threejs按鼠标位置缩放场景

threejs的orbitcontrol&#xff0c;默认的缩放模式为整体以target为中心进行缩放。有时候&#xff0c;我们想让场景按照鼠标位置进行缩放&#xff0c;体验起来就和地图的缩放一样&#xff0c;最直观的感觉就是整个场景会越来越靠近鼠标点的位置&#xff0c;而不是整体的缩放大小…

hibernate中多对多分解成一对多,

1&#xff0c;参考&#xff1a;http://blog.csdn.net/yaerfeng/article/details/6969632

C++ 函数参数 值传递与引用传递

以前我们在C语言中函数参数传递过程中&#xff0c;如果我们想要让当A函数作用域中的变量经过B函数处理之后的数值仍然在A函数中生效&#xff0c;这个时候函数参数的传递时需要引用方式去传递&#xff0c;方式如下&#xff1a; #include <stdio.h> //函数参数为指针&…

SharePoint 2013 图文开发系列之代码定义列表

在SharePoint的开发中&#xff0c;用Visual Studio自定义列表是经常会用到的&#xff0c;因为很多时候&#xff0c;我们并不会手动创建列表&#xff0c;而手动创建列表在测试服务器和正式机之间同步字段&#xff0c;也很麻烦&#xff0c;所以我们经常用代码来定义列表或者文档库…

arduino下载库出错_【arduino】DIY音乐播放器,arduino播放wav音乐,TRMpcm库测试及使用...

微信关注 “DLGG创客DIY”设为“星标”&#xff0c;重磅干货&#xff0c;第一时间送达。arduino特点库超多&#xff0c;想必大家都领教了&#xff0c;今天来分享一下之前玩过的TRMpcm库。这个库是干嘛用的&#xff1f;简单粗暴用arduino(这里特指arduino官方那几个板子uno、nan…

vim替换技巧4

、 转自&#xff1a;http://www.confay.com/2008/03/vim4.html [技巧一] 第一个是在VIM邮件列表中看到的&#xff0c;给出了一个如何统计文章字数的方法。 统计一个完整文件的字数&#xff0c;可以使用Unix下的wc工具&#xff0c;它能够统计一个文件的行数、单词数和字符数。 如…

spark1.x和2.xIterable和iterator兼容问题

1. spark 1.x 升级到spark 2.x 对于普通的spark来说,变动不大 : 1 举一个最简单的实例:spark1.x public static JavaRDD<String> workJob(JavaRDD<String> spark1Rdd) {JavaPairRDD<String, Integer> testRdd spark1Rdd.flatMapToPair(new PairFlatMapFunct…

C++ 拷贝构造函数和重载赋值运算符的区别

文章目录拷贝构造函数重载赋值运算符赋值运算符和拷贝构造函数最大区别是赋值运算符没有新的对象生成&#xff0c;而拷贝构造函数会生成新的对象。 为了更加形象 准确得描述 赋值运算符和拷贝构造函数得区别&#xff0c;将详细通过代码展示两者之间得差异。 拷贝构造函数 首先…

单元格内多个姓名拆分成一列_EXCEL拆分单元格中的姓名,这都不叫事儿

作者&#xff1a;祝洪忠 转自&#xff1a;Excel之家ExcelHome小伙伴们好啊&#xff0c;今天老祝和大家来分享一个数据整理的技巧。下面的表格形式&#xff0c;想必大家不会陌生吧&#xff1a;在这个表格内&#xff0c;同一个部门的人员名单都挤到一个单元格内。现在问题来了&am…