C++中的智能指针:shared_ptr

2023-05-16

本文主要参考
std::shared_ptr
【C++】shared_ptr共享型智能指针详解
std::shared_ptr 是一种智能指针,它能够记录多少个 shared_ptr 共同指向一个对象,从而消除显式的调用 delete,每多一个shared_ptr指向同一个对象,该对象的计数就会+1,当引用计数变为零的时候就会将对象自动删除。

共享指针的初始化方式

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内存模型

图片转载自【C++】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方式内存分布对比图:
图片转载自【C++】shared_ptr共享型智能指针详解
在这里插入图片描述

缺点

  • 所有的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;  //编译错误
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

C++中的智能指针:shared_ptr 的相关文章

随机推荐

  • 【测试】QGC地面站开发课程完结篇--一站多机控制测试说明

    阿木实验室去年推出的QGC地面站开发实战课程 xff0c 随着地面站控制多架飞机的测试的成功 xff0c 课程全部完结 xff0c 以下是我们户外测试最终版地面站的测试视频 xff1a 视频地址 xff1a https v qq com x
  • sdf文件使用plugin

  • VGG16训练RAF-DB

    使用VGG16对本地数据集RAF DB中的basic图片进行训练 xff0c 官方已经在图片命名时分好了train与test xff0c train和test的label在同一个txt文件里 xff0c 方便起见 xff0c 把这两种lab
  • CMake编译opencv(测试)

    WORKIGN FOR THE WOLF 单编译OpenCV来测试项目 项目名称 span class token operator span 自定义 span class token function project span span
  • 从零开始学习SLAM:openCV

    继续跟随 视觉SLAM十四讲 学习SLAM问题 xff0c 由于理论方面已经有一些研究 xff0c 主要缺乏的是在LINUX下的实战开发能力 xff0c 因而从代码开始分析入手 xff0c 同时对C 43 43 11进行回顾 1 openC
  • 对博士学位说永别

    来自王垠 xff1a http blog sina com cn s blog 5d90e82f0101atzr html 经过深思熟虑之后 xff0c 我决定再次 抛弃 我的博士学位 这是我第三次决定离开博士学位 xff0c 也应该是最后
  • python web开发——Django基于类的视图

    简介 视图是一个可调用对象 xff0c 可以接收一个请求然后返回一个响应 这个可调用对象不仅仅限于函数 xff0c Django 同时提供一些可以用作视图的类 它们允许你结构化你的视图并且利用继承和混合重用代码 后面我们将介绍一些用于简单任
  • 使用docker安装ubuntu镜像

    使用docker安装ubuntu镜像 查找Ubuntu镜像 docker search ubuntu 安装Ubuntu镜像 docker pull ubuntu 查看docker镜像 docker images 运行docker镜像 doc
  • Ubuntu安装kalibr

    Ubuntu安装kalibr错误集锦 一 安装过程 ros参考 xff1a https blog csdn net Mua111 article details 107513509 kalibr安装参考 xff1a https blog c
  • 树莓派4b ubuntu系统开启串口

    树莓派4b安装ubuntu server18后如何开启串口 xff1f 树莓派4b的引脚图如下 xff1a 其中GPIO14和GPIO15是硬件串口 因为我安装的不是Raspian系统 xff0c 因此无法用raspi config打开该串
  • 惯性导航原理(1):导航坐标系及相互转换

    一 导航坐标系转换 坐标系介绍1 惯性坐标系 xff08 地心惯性坐标系 xff09 i系2 地球坐标系 xff08 地心地固坐标系 xff09 e系3 WGS 84坐标系 xff08 常用 xff09 blh坐标系4 当地水平地理坐标系g
  • win7系统下安装Ubuntu20.04.5系统保姆级教程

    一 制作u盘启动盘 准备工作 xff1a 一个空的8G大小的u盘 43 ultraISO软件 43 ubuntu系统的镜像文件 1 下载并安装ultraISO软件 下载地址 xff1a 百度网盘 请输入提取码 提取码 xff1a jv6a
  • 手把手带你免费打嘉立创pcb板

    手把手带你免费打嘉立创pcb板 前言一 熟悉规则二 下单1 下载安装下单助手2 领劵 注意 前言 嘉立创的新规则 xff1a 上个月消费没有满20的话只支持立创EDA画的板子 一 熟悉规则 嘉立创的免费规则和板子工艺要求如下 xff0c 大
  • ADRC学习与参数整定心得

    ADRC xff0c 中文名是自抗扰控制技术 继承了经典PID控制器的精华 xff0c 对被控对象的数学模型几乎没有任何要求 xff0c 又在其基础上引入了基于现代控制理论的状态观测器技术 xff0c 将抗干扰技术融入到了传统PID控制当中
  • ArUco相关

    ArUco相关 ArUco xff0c 一个开源的微型的现实增强库 https blog csdn net bashendixie5 article details 113769010 Aruco码估计相机位姿初步 xff01 xff01
  • ArUco

    文章目录 一 ArUco简介二 Marker和字典三 步骤1 创建Marker xff08 Marker Creation xff09 2 检测Marker xff08 Marker Detection xff09 3 姿态估计 xff08
  • Python PIP升级后执行命令报错: sys.stderr.write(f“ERROR: {exc}“)解决方法

    近日在使用pip时终端始终提示 You are using pip version 8 1 1 however version 21 0 1 is available You should consider upgrading via th
  • 戴尔Optiplex-7080装ubuntu16.04双系统时遇到的一些坑

    目录 ubuntu16 04安装过程中遇到的坑安装教程安装类型为空 xff08 读取不到磁盘 xff09 ubuntu16 04安装完成后无法启动windows ubuntu16 04无法连接无线网络 ubuntu16 04安装过程中遇到的
  • Ubuntu 16.04无法检测双屏/nvidia-smi显示no running processing found/nvidia-settings读取不到显卡信息

    问题 xff1a 1 笔记本ubuntu 16 04系统无法检测双屏 xff0c xrandr不显示hdmi接口或hdmi disconnectted 2 nvidia smi的最下方显示No running processes found
  • C++中的智能指针:shared_ptr

    本文主要参考 std shared ptr C 43 43 shared ptr共享型智能指针详解 std shared ptr 是一种智能指针 xff0c 它能够记录多少个 shared ptr 共同指向一个对象 xff0c 从而消除显式