我的编程空间,编程开发者的网络收藏夹
学习永远不晚

C++智能指针之shared_ptr详解

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

C++智能指针之shared_ptr详解

共享指针的初始化方式

1.裸指针直接初始化,但不能通过隐式转换来构造

2.允许移动构造,也允许拷贝构造

3.通过make_shared构造

例:

#include <iostream>
#include <memory>
class Frame {};
int main()
{
  std::shared_ptr<Frame> f(new Frame());              // 裸指针直接初始化
  std::shared_ptr<Frame> f1 = new Frame();            // Error,explicit禁止隐式初始化
  std::shared_ptr<Frame> f2(f);                       // 拷贝构造函数
  std::shared_ptr<Frame> f3 = f;                      // 拷贝构造函数
  f2 = f;                                             // copy赋值运算符重载
  std::cout << f3.use_count() << " " << f3.unique() << std::endl;
  std::shared_ptr<Frame> f4(std::move(new Frame()));        // 移动构造函数
  std::shared_ptr<Frame> f5 = std::move(new Frame());       // Error,explicit禁止隐式初始化
  std::shared_ptr<Frame> f6(std::move(f4));                 // 移动构造函数
  std::shared_ptr<Frame> f7 = std::move(f6);                // 移动构造函数
  std::cout << f7.use_count() << " " << f7.unique() << std::endl;
  std::shared_ptr<Frame[]> f8(new Frame[10]());             // Error,管理动态数组时,需要指定删除器
  std::shared_ptr<Frame> f9(new Frame[10](), std::default_delete<Frame[]>());
  auto f10 = std::make_shared<Frame>();               // std::make_shared来创建
  return 0;
}

注意:

1.尽量避免将一个裸指针传递给std::shared_ptr的构造函数,常用的替代手法是使用std::make_shared。如果必须将一个裸指针传递给shared_ptr的构造函数,就直接传递new运算符的结果,而非传递一个裸指针变量。
2.不要将this指针返回给shared_ptr。当希望将this指针托管给shared_ptr时,类需要继承自std::enable_shared_from_this,并且从shared_from_this()中获得shared_ptr指针。
3.不要使用相同的原始指针作为实参来创建多个shared_ptr对象,具体原因见下面讲的shared_ptr内存模型。可以使用拷贝构造或者直接使用重载运算符=进行操作

例:

#include <iostream>
#include <memory>
class Frame {};
int main()
{
  Frame* f1 = new Frame();
  std::shared_ptr<Frame> f2(f1);
  std::shared_ptr<Frame> f3(f1);          // Error
  std::shared_ptr<Frame> f4(f2);
  auto f5 = f2;
  return 0;
}

常用成员函数

s.get():返回shared_ptr中保存的裸指针;

s.reset(…):重置shared_ptr;

  • reset( )不带参数时,若智能指针s是唯一指向该对象的指针,则释放,并置空。若智能指针P不是唯一指向该对象的指针,则引用计数减少1,同时将P置空。
  • reset( )带参数时,若智能指针s是唯一指向对象的指针,则释放并指向新的对象。若P不是唯一的指针,则只减少引用计数,并指向新的对象。如:
auto s = make_shared<int>(100);
s.reset(new int (200));

s.use_count():返回shared_ptr的强引用计数;

s.unique():若use_count()为1,返回true,否则返回false。

具体实例:

auto pointer = std::make_shared<int>(10);
auto pointer2 = pointer; // 引用计数+1
auto pointer3 = pointer; // 引用计数+1
int *p = pointer.get(); // 这样不会增加引用计数
std::cout << "pointer.use_count() = " << pointer.use_count() << std::endl; // 3
std::cout << "pointer2.use_count() = " << pointer2.use_count() << std::endl; // 3
std::cout << "pointer3.use_count() = " << pointer3.use_count() << std::endl; // 3
pointer2.reset();
std::cout << "reset pointer2:" << std::endl;
std::cout << "pointer.use_count() = " << pointer.use_count() << std::endl; // 2
std::cout << "pointer2.use_count() = " << pointer2.use_count() << std::endl; // 0, pointer2 已 reset
std::cout << "pointer3.use_count() = " << pointer3.use_count() << std::endl; // 2
pointer3.reset();
std::cout << "reset pointer3:" << std::endl;
std::cout << "pointer.use_count() = " << pointer.use_count() << std::endl; // 1
std::cout << "pointer2.use_count() = " << pointer2.use_count() << std::endl; // 0
std::cout << "pointer3.use_count() = " << pointer3.use_count() << std::endl; // 0, pointer3 已 reset

shared_ptr内存模型

在这里插入图片描述

由图可以看出,shared_ptr包含了一个指向对象的指针和一个指向控制块的指针。每一个由shared_ptr管理的对象都有一个控制块,它除了包含强引用计数、弱引用计数之外,还包含了自定义删除器的副本和分配器的副本以及其他附加数据。

控制块的创建规则

  • std::make_shared总是创建一个控制块;
  • 从具备所有权的指针出发构造一个std::shared_ptr时,会创建一个控制块(如std::unique_ptr转为shared_ptr时会创建控制块,因为unique_ptr本身不使用控制块,同时unique_ptr置空);
  • 当std::shared_ptr构造函数使用裸指针作为实参时,会创建一个控制块。这意味从同一个裸指针出发来构造不止一个std::shared_ptr时会创建多重的控制块,也意味着对象会被析构多次。如果想从一个己经拥有控制块的对象出发创建一个std::shared_ptr,可以传递一个shared_ptr或weak_ptr而非裸指针作为构造函数的实参,或者直接使用重载运算符=,这样则不会创建新的控制块。

因此,更好的解决方式是尽量避免使用裸指针作为共享指针的实参,而是使用make_shared,此外,make_shared相比直接new还具有以下好处

make_shared的优缺点

优点

  • 避免代码冗余:创建智能指针时,被创建对象的类型只需写1次,而用new创建智能指针时,需要写2次;
  • 异常安全:make系列函数可编写异常安全代码,改进了new的异常安全性;
  • 提升性能:编译器有机会利用更简洁的数据结构产生更小更快的代码。使用make_shared时会一次性进行内存分配,该内存单块(single chunck)既保存了T对象又保存与其相关联的控制块。而直接使用new表达式,除了为T分配一次内存,还要为与其关联的控制块再进行一次内存分配。

make_shared与new方式内存分布对比图:

在这里插入图片描述

缺点

  • 所有的make系列函数都不允许自定义删除器
  • make系列函数创建对象时,不能接受{}初始化列表(这是因为完美转发的转发函数是个模板函数,它利用模板类型进行推导。因此无法将{}推导为initializer_list)。换言之,make系列只能将圆括号内的形参完美转发;
  • **自定义内存管理的类(如重载了operator new和operator delete),不建议使用make_shared来创建。**因为:重载operator new和operator delete时,往往用来分配和释放该类精确尺寸(sizeof(T))的内存块;而make_shared创建的shared_ptr,是一个自定义了分配器(std::allocate_shared)和删除器的智能指针,由allocate_shared分配的内存大小也不等于上述的尺寸,而是在此基础上加上控制块的大小;
  • 对象的内存可能无法及时回收。因为:make_shared只分配一次内存,减少了内存分配的开销,使得控制块和托管对象在同一内存块上分配。而控制块是由shared_ptr和weak_ptr共享的,因此两者共同管理着这个内存块(托管对象+控制块)。当强引用计数为0时,托管对象被析构(即析构函数被调用),但内存块并未被回收,只有等到最后一个weak_ptr离开作用域时,弱引用也减为0才会释放这块内存块。原本强引用减为0时就可以释放的内存, 现在变为了强引用和弱引用都减为0时才能释放, 意外的延迟了内存释放的时间。这对于内存要求高的场景来说, 是一个需要注意的问题。

引用计数

  • shared_ptr中的引用计数直接关系到何时是否进行对象的析构,因此它的变动尤其重要。
  • shared_ptr的**构造函数会使该引用计数递增,而析构函数会使该计数递减。**但移动构造表示从一个己有的shared_ptr移动构造到一个新的shared_ptr。这意味着一旦新的shared_ptr产生后,原有的shared_ptr会被置空,其结果是引用计数没有变化;
  • 拷贝赋值操作同时执行两种操作(如sp1和sp2是指向不同对象的shared_ptr,则执行sp1=sp2时,将修改sp1使得其指向sp2所指的对象。而最初sp1所指向的对象的引用计数递减,同时sp2所指向的对象引用计数递增);
  • reset函数,如果不带参数时,则引用计数减1。如果带参数时,如sp.reset( p )则sp原来指向的对象引用计数减1,同时sp指向新的对象( p );
  • 如果实施一次递减后最后的引用计数变成0,即不再有shared_ptr指向该对象,则会被shared_ptr析构掉;
  • 引用计数的递增和递减是原子操作,即允许不同线程并发改变引用计数。

比较运算符

所有比较运算符都会调用共享指针内部封装的原始指针的比较运算符;支持==、!=、<、<=、>、>=;同类型的共享指针才能使用比较运算符

shared_ptr<int> sp_n1 = make_shared<int>(1);
shared_ptr<int> sp_n2 = make_shared<int>(2);
shared_ptr<int> sp_nu;
shared_ptr<double> sp_d1 = 
    make_shared<double>(1);
bool bN1LtN2 = sp_n1 < sp_n2;  //true
bool bN1GtNu = sp_n1 > sp_nu;  //true
bool bNuEqNu = sp_nu == sp_nu; //true
bool bN2GtD1 = sp_d1 < sp_n2;  //编译错误

总结

本篇文章就到这里了,希望能够给你带来帮助,也希望您能够多多关注编程网的更多内容!    

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

C++智能指针之shared_ptr详解

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

C++特性之智能指针shared_ptr详解

shared_ptr是C++11提供的一种智能指针类,它足够智能,可以在任何地方都不使用时自动删除相关指针,从而帮助彻底消除内存泄漏和悬空指针的问题。本文主要是来和大家聊聊shared_ptr的使用,需要的可以参考一下
2022-12-08

C++智能指针之shared_ptr如何使用

这篇文章主要介绍“C++智能指针之shared_ptr如何使用”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“C++智能指针之shared_ptr如何使用”文章能帮助大家解决问题。std::share
2023-06-30

C++智能指针shared_ptr怎么使用

本篇内容介绍了“C++智能指针shared_ptr怎么使用”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!1、什么是shared_ptr?C+
2023-06-29

C++11智能指针shared_ptr怎么使用

本篇内容介绍了“C++11智能指针shared_ptr怎么使用”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!shared_prt的本身是一个
2023-06-19

C语言智能指针shared_ptr和weak_ptr怎么用

这篇文章主要讲解了“C语言智能指针shared_ptr和weak_ptr怎么用”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“C语言智能指针shared_ptr和weak_ptr怎么用”吧!w
2023-06-30

浅析Boost智能指针:scoped_ptr shared_ptr weak_ptr

虽然通过弱引用指针可以有效的解除循环引用,但这种方式必须在程序员能预见会出现循环引用的情况下才能使用,也可以是说这个仅仅是一种编译期的解决方案,如果程序在运行过程中出现了循环引用,还是会造成内存泄漏的
2022-11-15

C/C++中智能指针的用法详解

C/C++中,指针是一个非常重要的概念,其强大但也麻烦,麻烦之处就在于一旦你申请了内存,那就必须要手动去释放内容,否则就会造成内存泄漏。所以智能指针的作用就是防止我们麻痹大意忘记释放内存,帮助我们管理内存的,本文就来聊聊智能指针的用法
2023-01-04

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录