【C++11智能指针】shared_ptr的初始化、拷贝构造和拷贝赋值、移动构造和移动赋值

2023-11-14

1.智能指针概述

直接用 new 返回的指针称为“裸指针”,智能指针可以理解为对“裸指针”进行了包装,解决“裸指针”可能带来的各种问题。

智能指针最突出的优点是能够自动释放所指向的对象内存,帮助我们进行动态分配对象(new出来的对象)的生命周期的管理,能够有效防止内存泄漏。

C++标准库有四种智能指针:

  • auto_ptr:C++98,目前 auto_ptr 已经完全被 unique_ptr 取代,C++11标准中不推荐使用(弃用)auto_ptr。

  • unique_ptr:C++11,独占式指针。同一个时间内,只有一个指针能够指向该对象。当然,该对象的所有权还是可以移交出去的。

  • shared_ptr:C++11,共享式指针。多个指针指向同一个对象,最后一个指针被销毁时,这个对象会被释放。

  • weak_ptr:C++11,weak_ptr 是辅助 shared_ptr 工作的。

2.shared_ptr的初始化

2.1 shared_ptr和new结合使用(直接初始化)

unique_ptrshared_ptrweak_ptr 都是类模板,因此,当我们创建一个智能指针时,必须提供额外的信息,即指针可以指向的类型。与 vector 一样,我们在尖括号内给出类型,之后是所定义的这种智能指针的名字:

shared_ptr<string> p1; // shared_ptr可以指向string
shared_ptr<list<int>> p2; // shared_ptr可以指向int的list

默认初始化的智能指针中保存着一个空指针。

shared_ptr<int> pi; // 指向int的空智能指针,pi==nullptr
shared_ptr<int> pi(new int(100)); // pi指向一个值为100的int型数据

接受指针参数的智能指针构造函数是 explicit 的,因此,我们不能将一个内置指针隐式转换为一个智能指针,必须使用直接初始化形式来初始化一个智能指针:

shared_ptr<int> p1 = new int(1024); // 错误
shared_ptr<int> p2(new int(1024)); // 正确

出于相同的原因,一个返回 shared_ptr 的函数不能在其返回语句中隐式转换一个普通指针:

shared_ptr<int> clone(int p) {
	return new int(p); // 错误
}

我们必须将 shared_ptr 显式绑定到一个想要返回的指针上:

#include<iostream>
using namespace std;

shared_ptr<int> clone(int p) {
	return shared_ptr<int>(new int(p)); // 正确
}

int main() {

	shared_ptr<int> pi3 = clone(130);
	
	return 0;
}

2.2 make_shared函数

最安全的分配和使用动态内存的方法是调用一个名为 make_shared 的标准库函数。

此函数在动态内存(堆)中分配一个对象并初始化它,返回指向此对象的 shared_ptr。

与智能指针一样,make_shared 也定义在头文件 memory 中。

当要用 make_shared 时,必须指定想要创建的对象的类型。定义方式与模板类相同,在函数名之后跟一个尖括号,在其中给出类型:

// 指向一个值为42的int的shared_ptr
shared_ptr<int> p3 = make_shared<int>(42);
// p4指向一个值为"9999999999"的string
shared_ptr<string> p4 = make_shared<string>(10, '9');

make_shared 用其参数来构造给定类型的对象。例如,调用 make_shared<string> 时传递的参数必须与 string 的某个构造函数相匹配,调用 make_shared<int> 时传递的参数必须能用来初始化一个 int,依此类推。

如果我们不传递任何参数,对象就会进行值初始化。

// p5指向一个值初始化的int,即值为0
shared_ptr<int> p5 = make_shared<int>();

当然,我们通常用 auto 定义一个对象来保存 make_shared 的结果,这种方式较为简单:

// p6指向一个动态分配的空vector<string>
auto p6 = make_shared<vector<string>>();

3.shared_ptr的拷贝构造和拷贝赋值

当进行拷贝或赋值操作时,每个 shared_ptr 都会记录有多少个其他 shared_ptr 指向相同的对象:

auto p = make_shared<int>(42); // p指向的对象只有p一个引用者
auto q(p); // p和q指向相同对象,此对象有两个引用者

我们可以认为每个 shared_ptr 都有一个关联的计数器,通常称其为引用计数(reference count)

无论何时我们拷贝一个 shared_ptr,计数器都会递增。例如,当用一个 shared_ptr 初始化另一个 shared_ptr,或将它作为参数传递给一个函数以及作为函数的返回值时,它所关联的计数器就会递增。

当我们给 shared_ptr 赋予一个新值或是 shared_ptr 被销毁(例如一个局部的 shared_ptr 离开其作用域)时,计数器就会递减。

一旦一个 shared_ptr 的计数器变为 0,它就会自动释放自己所管理的对象:

auto r = make_shared<int>(42); // r指向的int只有一个引用者
r = q;
// 给r赋值,令它指向另一个地址
// 递增q指向的对象的引用计数
// 递减r原来指向的对象的引用计数
// r原来指向的对象已没有引用者,会自动释放

在上面代码中,我们分配了一个 int,将其指针保存在 r 中。接下来,我们将一个新值赋予 r。在此情况下,r 是唯一指向此 int 的 shared_ptr,在把 q 赋给 r 的过程中,此 int 被自动释放。

提示:到底是用一个计数器还是其他数据结构来记录有多少指针共享对象,完全由标准库的具体实现来决定。关键是智能指针类能记录有多少个 shared_ptr 指向相同的对象,并能在恰当的时候自动释放对象。

4.shared_ptr的移动构造和移动赋值

#include <iostream>
#include <memory>
using namespace std;

int main()
{
    shared_ptr<int> sp1(new int(180)); // 强引用计数从0变1
    
    shared_ptr<int> sp2(std::move(sp1)); // 移动构造一个新的智能指针对象sp2,sp1变成空,sp2指向该内存,强引用计数仍为1

	return 0;
}
#include <iostream>
#include <memory>
using namespace std;

int main()
{
    shared_ptr<int> sp1(new int(180)); // 强引用计数从0变1

    shared_ptr<int> sp2;

    sp2 = std::move(sp1); // 移动赋值,sp1变成空,sp2指向该内存,强引用计数仍为1

	return 0;
}

5.shared_ptr自动销毁所管理的对象

当指向一个对象的最后一个 shared_ptr 被销毁时,shared_ptr 类会自动销毁此对象。它是通过析构函数完成销毁工作的。

类似于构造函数,每个类都有一个析构函数。就像构造函数控制初始化一样,析构函数控制此类型的对象销毁时做什么操作。析构函数一般用来释放对象所分配的资源。例如,string 的构造函数会分配内存来保存构成 string 的字符,string 的析构函数就负责释放这些内存。类似的,vector 的若干操作都会分配内存来保存其元素,vector 的析构函数就负责销毁这些元素,并释放它们所占用的内存。

shared_ptr 的析构函数会递减它所指向的对象的引用计数。如果引用计数变为 0 0 0,shared_ptr 的析构函数就会销毁对象,并释放它占用的内存。

6.shared_ptr自动释放相关联的内存

当动态对象不再被使用时,shared_ptr 类会自动释放动态对象,这一特性使得动态内存的使用变得非常容易。

例如,在下面代码中,create() 函数返回一个 shared_ptr,指向一个 int 类型的动态分配的对象,对象是通过一个类型为 int 的参数进行初始化的。由于 create() 函数返回一个 shared_ptr,所以我们可以确保它分配的对象会在恰当的时刻被释放。

// create()函数返回一个shared_ptr,指向一个动态分配的对象
shared_ptr<int> create(int value)
{
    return make_shared<int>(value); // shared_ptr负责释放内存
}

例如,在下面代码中,fun() 函数将 create() 函数返回的 shared_ptr 保存在局部变量 p 中,由于 p 是 fun() 函数的局部变量,在 fun() 函数结束时它将被销毁。当 p 被销毁时,将递减其引用计数并检查它是否为 0 0 0。在此例中,p 是唯一引用 create() 函数返回的内存的对象。由于 p 将要销毁,p 指向的这个对象也会被销毁,所占用的内存会被释放。

void fun(int value)
{
    shared_ptr<int> p = create(value); // p离开了作用域,它指向的内存会被自动释放掉
    return;
}

int main()
{
    fun(12);
    return 0;
}

但如果有其他 shared_ptr 也指向这块内存,它就不会被释放掉。在下面代码中,fun() 函数中的 return 语句向此函数的调用者返回一个 p 的拷贝。拷贝一个 shared_ptr 会增加所管理对象的引用计数值。现在当 p 被销毁时,它所指向的内存还有其他使用者。对于一块内存,shared_ptr 类保证只要有任何 shared_ptr 对象引用它,它就不会被释放掉。

shared_ptr<int> fun(int value)
{
    shared_ptr<int> p = create(value);
    return p; // 当我们返回p时,引用计数进行了递增操作
} // p离开了作用域,但它指向的内存不会被释放掉

int main()
{
    auto sp = fun(12);
    return 0;
}

由于在最后一个 shared_ptr 销毁前内存都不会释放,保证 shared_ptr 在无用之后不再保留就非常重要了。如果你忘记了销毁程序中不再需要的 shared_ptr,程序仍会正确执行,但会浪费内存。shared_ptr 在无用之后仍然保留的一种可能情况是,你将 shared_ptr 存放在一个容器中,随后重排了容器,从而不再需要某些元素。在这种情况下,你应该确保用 erase 删除那些不再需要的 shared_ptr 元素。

提示:如果你将 shared_ptr 存放于一个容器中,而后不再需要全部元素,而只使用其中一部分,要记得用 erase 删除不再需要的那些元素。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

【C++11智能指针】shared_ptr的初始化、拷贝构造和拷贝赋值、移动构造和移动赋值 的相关文章

随机推荐

  • JavaFX——添加图片ImageView

    Image 用于加载图片文件ImageView 用于显示图片位置 Javafx scence image Image 目前支持BMP GIF JPEG PNG JPG图片格式 Image image new Image URL 其中 URL
  • 在Xcode中使用Git进行源码版本控制

    本文翻译自Understanding Git Source Control in Xcode 译者myShire 欢迎您加入我们的翻译小组 在应用程序开发过程中 很重要的一部分工作就是如何进行源码的版本控制 当代码出现问题时 我们就需要将代
  • 计算机网络速成课【体系结构】

    计算机网络体系结构 计算机网络概述 计算机网络 组成 从组成部分上看 一个完整的计算机网络主要由硬件 软件 协议三大部分组成 缺一不可 硬件主要指 主机 通信链路 交换设备和通信设备等 软件主要指 用户使用的各种软件 协议指 网络传输数据时
  • 【通览一百个大模型】Anthropic LLM(Anthropic)

    通览一百个大模型 Anthropic LLM Anthropic 作者 王嘉宁 本文章内容为原创 仓库链接 https github com wjn1996 LLMs NLP Algo 订阅专栏 大模型 NLP 算法 可获得博主多年积累的全
  • Office2016+math type出错 word无法加载此加载程序

    今天遇到一个非常非常非常奇葩的问题 关于网上的答案 想必大部分人都试过 但是如果你不幸看到我这篇文章 那么恭喜你 这个问题会让你无语 首先贴出问题 说明一下配置 我的电脑是64位 安装的office也是64位 微软诚不欺我 个蛋 就在我翻来
  • Redis两种客户端:lettuce和Jedis的区别

    spring boot 2的spring boot starter data redis中 默认使用的是lettuce作为redis客户端 它与jedis的主要区别如下 Jedis是同步的 不支持异步 Jedis客户端实例不是线程安全的 需
  • 【MATLAB傅里叶级数3D动画演示】

    傅里叶级数3D动画演示 编写环境 MATLAB2021b classdef Msg lt event EventData MSG 定义事件消息 event EventData子类 消息中封装有要发布的数据 Data 数据可以是任意类型 pr
  • Jmeter之接口测试流程详解

    前言 今天笔者呢 想给大家聊聊Jmeter接口测试流程详解 废话不多说直接进入正题 一 jmeter简介 Jmeter是由Apache公司开发的java开源项目 所以想要使用它必须基于java环境才可以 Jmeter采用多线程 允许通过多个
  • C#简单练习一

    namespace Demo练习 internal class Program static void Main string args 输入a b两个整数 编程求出a除以b得到的商和余数 输入 两行 只有两个整数 输出 输出只有一行 两个
  • 使用GPU训练(PyTorch demo)

    cuda 对网络模型 数据 损失函数这三种变量调用 cuda 来在GPU上进行训练 将网络模型在gpu上训练 model Model model model cuda 损失函数在gpu上训练 loss fn nn CrossEntropyL
  • PCL调错:合集

    1 error C4996 pcl visualization PointCloudColorHandler
  • QT-线程池

    在程序逻辑中经常会碰到需要处理大批量任务的情况 比如密集的网络请求 或者日志分析等等 一般会创建一个队列 用一个或者多个线程去消费这个队列 一般也要处理队列的加锁和解锁的问题 除非在设计时就能够做到专列专用 否则锁是不可避免的 而且在入队和
  • Kubernetes v1.26 配置默认存储 StorageClass

    Kubernetes v1 25 引入了一个 Alpha 特性来更改默认 StorageClass 被分配到 PersistentVolumeClaim PVC 的方式 启用此特性后 你不再需要先创建默认 StorageClass 再创建
  • vue所有UI库通用)tree-select 下拉多选(设置 maxTagPlaceholder 隐藏 tag 时显示的内容,支持鼠标悬浮展示更多

    如果可以实现记得点赞分享 谢谢老铁 1 需求描述 引用的下拉树形结构支持多选 限制选中tag的个数 且超过制定个数 鼠标悬浮展示更多已选中 2 先看下效果图 3 实现思路 首先根据API文档 先设置maxTagCount 最多显示多少个 t
  • networkx创建带权有向图,访问每个点的邻居节点(neighbor)和边权(weight)

    import networkx as nx G nx DiGraph 创建有向图 G add edge 1 2 weight 1 添加 带权边 weight表示边权 G add edge 1 3 weight 1 G add edge 3
  • eureka缓存

    AP系统 服务端 三级缓存 缓存 说明 一级 本地缓存 实时更新 客户端注册时数据保存到这 二级 读写缓存 实时更新 客户端注册 下线 故障时缓存失效 读取读写缓存找不到数据时 去一级缓存读取并保存到二级缓存 三级 读缓存 周期更新 默认3
  • watch的使用方法

    watch简单监听属性 监听对象就不要用这种写法 data return num 1 watch num newval oldval newval 是新值 oldval 是修改前的值 num有变化之后所执行的代码块 console log
  • 学习总结Q

    学习总结 学习内容 Java HashSet 学习产出 HashSet 基于 HashMap 来实现的 是一个不允许有重复元素的集合 HashSet 允许有 null 值 HashSet 是无序的 即不会记录插入的顺序 HashSet 不是
  • MySQL 的CASE WHEN 语句使用说明

    介绍mysql数据库中case when语句的用法 首先介绍case when语句的基础知识 然后提供了相关例子 1 mysql数据库中CASE WHEN语句 case when语句 用于计算条件列表并返回多个可能结果表达式之一 CASE
  • 【C++11智能指针】shared_ptr的初始化、拷贝构造和拷贝赋值、移动构造和移动赋值

    文章目录 1 智能指针概述 2 shared ptr的初始化 2 1 shared ptr和new结合使用 直接初始化 2 2 make shared函数 3 shared ptr的拷贝构造和拷贝赋值 4 shared ptr的移动构造和移