C++ 智能指针常用总结

C++ 智能指针常用总结

文章目录

  • C++ 智能指针常用总结
    • 1. 写在对前面
    • 2. why 智能指针
    • 3. what 智能指针
      • 3.1 unique_ptr
      • 3.2 shared_ptr
      • 3.3 weak_ptr
    • 3. how 指针指针
      • 3.1 unique_ptr
        • 3.1.1 创建
        • 3.1.2 成员函数
      • 3.2 shared_ptr
        • 3.2.1创建
        • 3.2.2 成员对象
      • 3.3 weak_ptr
    • 4. 碎碎念
    • 5.参考资料

1. 写在对前面

最近接了一个写 c++ 的任务,在使用智能指针的时候,写出一个 dump 的 bug。作为 golang 小白在入门 c++ 的必经之路。整理一份 c++ 智能指针的常用知识点。全文按照如下思路整理

  • why 智能指针

  • what 智能指针

  • how 智能指针

智能指针是 C++ 中用于管理动态分配对象的一种特殊指针。它能够自动管理对象的生命周期。避免内存泄漏和野指针的问题。

2. why 智能指针

一种特性的提出,势必是为了解决某类难以解决的问题。」在智能指针提出之前,C++ 是通过手动分配和释放内存来进行管理的。使用如下两个关键字进行分配和释放内存:

  • new:用于分配动态内存。通过使用 new 关键字,可以在堆上分配指定大小的内存,并返回指向新分配的内存的指针。例如,可以使用 int* p = new int; 来分配一个整数的内存,并将指向该内存的指针存储在变量 p 中。

  • delete:用于释放动态内存。使用 delete 关键字,可以释放通过 new 关键字分配的内存。例如,可以使用 delete p; 来释放变量 p 指向的整数内存。

这种原始的内存管理方式需要程序员手动追踪和管理分配和释放的内存,容易引发一系列问题,比如内存泄漏、重复释放、野指针等。因此,为了简化内存管理的过程并减少错误,C++ 引入智能指针和 RAII (资源获取即初始化) 等方式来自动管理和释放内存。

注:资源获取即初始化(Resource Acquisition Is Initialization,RAII) 是一种 C++ 编程技术,通过在对象的构造函数中获取资源,并在析构函数中释放资源,以确保资源在对象生命周期内始终被争取管理。

RAII 的核心思想,将资源的生命周期与对象的生命周期绑定在一起。当对象被创建时,它获取了所需的资源;当对象被销毁时,它释放了持有的资源。

3. what 智能指针

智能指针有多种实现,包括 std::shared_ptr、std::unique_ptr 和 std::weak_ptr。要理解这三类智能指针,需要从「对象所有权」的概念入手。

  1. 每个独占的资源都应该有一个所有者 owner。

  2. 被共享的资源需要通过引用计数来管理。对应的实现是 shared_ptr。

  3. 借用资源的时候,可以使用 raw point 或者引用。但更推荐使用引用

  4. 如果有循环引用,需要打破循环,可能的办法有借助 weak pointer。

  5. 资源的所有权可以被转移,比如 move。

  6. 资源的所有者负责释放资源,资源的借用者不能释放资源。

指针解决的是第1、2、4 这三类问题。

3.1 unique_ptr

unique_ptr 代表的是专属所有权(exclusive ownership),即由 unique_ptr 管理的内存,只能被一个对象持有。

注:大多数场景下应用到的应该是 unique_ptr

性能:c++ 的 zero cost abstraction 的特点,unique_ptr 在默认的情况下和裸指针的大小是一样的。所以在内存上没有任何的额外消耗,性能最优。

3.2 shared_ptr

shared_ptr 代表共享所有权(shared ownership),即多个 shared_ptr 可以共享同一块内存。

性能

  • 「内存占用高」:shared_ptr 的内存占用是裸指针的两倍。因为除了要管理一个裸指针外,还需要维护一个引用计数。

  • 「原子操作性能低」:考虑到线程安全问题,引用计数的增减必须是原子操作。而原子操作一般情况下都比非原子操作慢。

  • 「使用移动优化性能」:shared_ptr 在性能是虽然低于 unique_ptr。但是可以通过使用 std::move 来优化性能。在「复制 (a = b)」与「移动(a = std::move(b))」之间,建议选择后者。因为移动不用增加引用计数

3.3 weak_ptr

weak_ptr 解决了 shared_ptr 存在的「循环引用」问题。它不会增加引用计数,也不会阻止资源的销毁。

避免循环引用:在对象之间存在循环引用时,使用 shared_ptr 会到导致资源无法释放。而 weak_ptr 可以打破循环引用,因为它不增加引用计数。当所有的 shared_ptr 释放之后,资源会被争取的释放。

导致循环引用的示例:

class B;
struct A{shared_ptr<B> b;
};
struct B{shared_ptr<A> a;
};
auto pa = make_shared<A>();
auto pb = make_shared<B>();
pa->b = pb;
pb->a = pa;

注:解除循环引用只需要 struct B 中的 shared_ptr(A) 替换为 weak_ptr(A)

性能:weak_ptr 具有比 shared_ptr 更高的性能。但是需要注意的是,由于 weak_ptr 不会增加引用计数,所以如果想要访问对象,需要提前判断对象是否存在,防止访问空指针。

3. how 指针指针

3.1 unique_ptr

3.1.1 创建
  1. 可以通过如下两种方式,构造出 unique_ptr 类型的空指针
std::unique_ptr<int> p1;          // 不传入任何实参
std::unique_ptr<int> p2(nullptr); // 传入空指针
  1. 在构建 unique_ptr 智能指针时,明确其指向
std::unique_ptr<int> p3(new int(20));
  1. 使用 std::make_unique 模板函数,可以用于初始化 unique_ptr 智能指针
std::unique_ptr<int> p4 = std::make_unique<int>(10);

注:std::make_unique 为 C++11 标准提供的函数

  1. 使用 std::move 移动构造函数
std::unique_ptr<int> p5(std::move(p4))
std::unique_ptr<int> p5 = std::move(p4)

注:使用 std::move(p4) 初始化 p5,会使得 p5 拥有 p4 的堆内存,而 p4 则变成空智能指针

  1. 在初始化 unique_ptr 智能指针时,还可以自定义所指堆内存的释放规则,可以在定义的释放规则中加入自定义的释放规则,以释放默写资源
//指定 default_delete 作为释放规则
std::unique_ptr<int> p6(new int[10], std::default_delete<int[]>());//自定义释放规则
void deleteInt(int*p) {delete []p;
}// lambda 表示缩写 deleteInt 函数
// [](int* p) {delete[]p; }//初始化智能指针,并自定义释放规则
std::unique_ptr<int> p7(new int[10], deleteInt);

特殊说明unique_ptr 不支持复制和赋值

auto w = std::make_unique<int>();
auto w2 = w; // 编译错误
3.1.2 成员函数
成员方法名功 能
operator=()重载赋值号
operator*()重载 * 号,获取当前unique_ptr 智能指针对象指向的数据。
operator->()重载 -> 号,当智能指针指向的数据类型为自定义的结构体时,通过 -> 运算符可以获取其内部的指定成员。
release()返回指向管理对象的指针并释放所有权。
reset()替换 unique_ptr 管理的对象。
swap()交换 2 个相同类型 unique_ptr 智能指针的内容。
get()获得 unique_ptr 对象内部包含的普通指针。
get_deleter()返回用于销毁管理对象的删除器
operator bool()判断当前 unique_ptr 对象是否为空智能指针,如果是空指针,返回 false;反之,返回 true。

3.2 shared_ptr

3.2.1创建
  • 可以通过如下两种方式,构造出 shared_ptr 类型的空指针

    std::shared_ptr<int> p1;          // 不传入任何实参
    std::shared_ptr<int> p2(nullptr); // 传入空指针
    
  • 在构建 shared_ptr 智能指针时,明确其指向

    std::shared_ptr<int> p3(new int(20));
    
  • 使用 std::make_shared 模板函数,可以用于初始化 shared_ptr 智能指针

    std::shared_ptr<int> p4 = std::make_shared<int>(10);
    

    注:std::make_shared 为 C++11 标准提供的函数

  • 使用 std::move 移动构造函数或者使用赋值构造

    // 赋值构造
    std::shared_ptr<int> p5(p4);
    std::shared_ptr<int> p5 = p4;// 移动构造
    std::unique_ptr<int> p5(std::move(p4))
    std::unique_ptr<int> p5 = std::move(p4)
    

    注:

    使用赋值构造会导致引用计数加 1,同一普通指针不能同时为多个 shared_ptr 对象赋值

    使用 std::move(p4) 初始化 p5,会使得 p5 拥有 p4 的堆内存,而 p4 则变成空智能指针

  • 在初始化 unique_ptr 智能指针时,还可以自定义所指堆内存的释放规则,可以在定义的释放规则中加入自定义的释放规则,以释放默写资源

    //指定 default_delete 作为释放规则
    std::shared_ptr<int> p6(new int[10], std::default_delete<int[]>());//自定义释放规则
    void deleteInt(int*p) {delete []p;
    }// lambda 表示缩写 deleteInt 函数
    // [](int* p) {delete[]p; }//初始化智能指针,并自定义释放规则
    std::shared_ptr<int> p7(new int[10], deleteInt);
    
3.2.2 成员对象
成员方法名功 能
operator=()重载赋值号,使得同一类型的 shared_ptr 智能指针可以相互赋值。
operator*()重载 * 号,获取当前 shared_ptr 智能指针对象指向的数据。
operator->()重载 -> 号,当智能指针指向的数据类型为自定义的结构体时,通过 -> 运算符可以获取其内部的指定成员。
swap()交换 2 个相同类型 shared_ptr 智能指针的内容。
reset()当函数没有实参时,该函数会使当前 shared_ptr 所指堆内存的引用计数减 1,同时将当前对象重置为一个空指针;当为函数传递一个新申请的堆内存时,则调用该函数的 shared_ptr 对象会获得该存储空间的所有权,并且引用计数的初始值为 1。
get()获得 shared_ptr 对象内部包含的普通指针。
use_count()返回同当前 shared_ptr 对象(包括它)指向相同的所有 shared_ptr 对象的数量。
unique()判断当前 shared_ptr 对象指向的堆内存,是否不再有其它 shared_ptr 对象再指向它。
operator bool()判断当前 shared_ptr 对象是否为空智能指针,如果是空指针,返回 false;反之,返回 true。

3.3 weak_ptr

weak_ptr 当前的代码库中使用的较少,后续代码用到单独总结其使用场景吧。

4. 碎碎念

虽然现在总结的知识都可以通过 chatgpt 问到,但是「学习」的目的在于自己学习、理解和分析,如果只是单纯的看的话,下次、下下次在遇到可能自己还是一知半解的照抄模式。

  • 我永远喜欢我自己,我有勇气敞开心扉去接受一起新的变化和想法,但我永远不会因为别人的反映而惶恐不安,不因外界的评价而自我怀疑,我允许自己被否定,但我无需认同,也不会在意。

  • 任何人在任何时间分道扬镳很正常。

  • 如果感觉有点累了,那就去喜欢的事物里喘口气。

5.参考资料

  • 关于 c++ 智能指针的使用场景。智能指针能完全替代 new\delete 吗?

  • 什么是 move? 理解 c++ value categories, move, move in Rust

  • RAII: 如何编写没有内存泄漏的代码 with C++

  • std::unique_ptr - cppreference.com

  • C++11 shared_ptr智能指针(超级详细)

  • C++转换构造函数:将其它类型转换为当前类的类型

  • C++拷贝构造函数(复制构造函数)详解

  • C++ 智能指针的正确使用方式 | 编程沉思录

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/113223.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

移动设备管理对企业IT 安全的增强

移动设备管理 &#xff08;MDM&#xff09; 是通过定义策略和部署安全控制&#xff08;如移动应用程序管理、移动内容管理和条件 Exchange 访问&#xff09;来管理移动设备的过程。 完整的MDM解决方案可以管理在Android&#xff0c;iOS&#xff0c;Windows&#xff0c;macOS&a…

Spring Boot配置多个Kafka数据源

一、配置文件 application.properties配置文件如下 #kafka多数据源配置 #kafka数据源一&#xff0c;日志审计推送 spring.kafka.one.bootstrap-servers172.19.12.109:32182 spring.kafka.one.producer.retries0 spring.kafka.one.producer.properties.max.block.ms5000 #kafk…

Elasticsearch 8.X 分词插件版本更新不及时解决方案

1、关于 Elasticsearch 8.X IK 分词插件相关问题 球友在 ElasticSearch 版本选型问题中提及&#xff1a;如果要使用ik插件&#xff0c;是不是就使用目前最新的IK对应elasticsearch的版本“8.8.2”&#xff1f; https://github.com/medcl/elasticsearch-analysis-ik/releases/ta…

python异常及解决方法汇总

文章目录 1、flask异常&#xff1a;TypeError: __init__() got an unexpected keyword argument unbound_message参考文献 1、flask异常&#xff1a;TypeError: init() got an unexpected keyword argument ‘unbound_message’ 解决方法&#xff1a; pip install Flask2.1.3…

怎么获取开源的商城源码

前言 开源的商城源码是指可以自由获取、使用和修改的商城程序代码&#xff0c;通常由开源社区或个人开发者贡献和维护。有许多开源的商城源码可以用于建立自己的商城网站&#xff0c;这篇文章将为您介绍如何找到这些源码。 GitHub搜索 GitHub是一个国际知名的开源代码托管平…

K8s 概念及组件

K8s 的全称为Kubernetes&#xff0c;是一种开源的容器编排平台&#xff0c;用于自动化部署以及扩展和管理容器化的应用程序&#xff0c;它提供了一种容器编排和管理的方式&#xff0c;可以帮助开发人员更轻松的管理容器化的应用程序&#xff0c;并且提供了一种跨多个主机的自动…

Jmeter性能测试 —— jmeter之使用ServerAgent监控服务器

ServerAgent 性能测试时我们关注的重要指标是&#xff1a;并发用户数&#xff0c;TPS&#xff0c;请求成功率&#xff0c;响应时间&#xff0c;服务器的CPU&#xff0c;memory&#xff0c; I/O disk等。Jmeter的聚合报告可以查看并发数、吞吐量、请求成功率、响应时间等&#…

Reasoning with Language Model Prompting: A Survey

本文是LLM系列的文章&#xff0c;针对《Reasoning with Language Model Prompting: A Survey》的翻译。 语言模型提示推理&#xff1a;综述 摘要1 引言2 前言3 方法分类4 比较和讨论5 基准与资源6 未来方向7 结论与视角 摘要 推理作为解决复杂问题的基本能力&#xff0c;可以…

ERR_PNPM_LINKING_FAILED Error: EPERM: operation not permitted, rename

webstorm终端pnpm报错  ERR_PNPM_LINKING_FAILED  Error: EPERM: operation not permitted, rename ’ 报错原因&#xff1a;powershell权限不够 解决办法&#xff1a;提升权限/在文件打开Powershell安装依赖

发现一款非常好用的学术GPT,可形成知识库,并分析论文,根据观点生成文字

发现一款非常好用的学术GPT&#xff0c;支持CHATGPT3.5交互、论文分析与生成&#xff0c;目前作者并未全面推广&#xff0c;仅在小圈子里使用&#xff0c;可以保证后端api的使用稳定性&#xff0c;不会出现大量用户共享gpt 服务&#xff0c;导致gpt调用超时的情况。 使用方法&a…

java经典面试题总结

1.请简述Java的继承&#xff0c;重写和多态的概念和运用 继承是一种Java中重要的面向对象编程方式&#xff0c;它允许一个类从另一个类继承某些属性和方法&#xff0c;在这种关系下&#xff0c;子类可以重写父类的方法&#xff0c;从而实现不同的行为。 多态是继承实现的一种关…

关系数据库-postgresql-基础

文章目录 介绍linux下安装postgresql源码安装navicat连接 介绍 Postgresql官网开源的关系型数据库&#xff1b; linux下安装 Ubuntu下可以使用apt包管理器安装&#xff1b;参考地址CentOS下可以使用yum包管理器安装&#xff1b;OpenSuse下可以使用zypper包管理器安装&#xf…

基于Python3的Scapy构造DNS报文

一&#xff1a;DNS协议 DNS&#xff08;Domain Name System&#xff09;协议是计算机网络中的一种基础协议&#xff0c;它用于将域名&#xff08;如www.baidu.com&#xff09;转换为IP地址&#xff08;如192.168.0.1&#xff09;&#xff0c;从而实现计算机之间的通信。 DNS 分…

React基础: 项目创建 JSX 基础语法 React基础的组件使用 useState状态 基础样式控制

01 React 文章目录 01 React一、React是什么1、React的优势 二、React开发环境搭建1、创建项目2、运行项目3、项目的目录结构 三、JSX基础1、什么是 JSX代码示例&#xff1a; 2、JSX使用场景2.1代码示例&#xff1a; 3、JSX中实现列表渲染4、JSX - 实现基本的条件渲染5、JSX - …

喜讯!持安科技入选2023年北京市知识产权试点单位!

近日&#xff0c;北京市知识产权局发布了“2023年度北京市知识产权试点示范单位及2020年度北京市知识产权试点示范单位复审通过名单”名单。 经过严格的初审、形式审核和专家评审&#xff0c;北京持安科技有限公司入选“2023年北京市知识产权试点单位”。 北京市知识产权试点示…

并发性Socket通信源码(基于linux环境下多线程)

服务器端&#xff1a;server.c 1 #include <stdio.h>2 #include <stdlib.h>3 #include <unistd.h>4 #include <string.h>5 #include <arpa/inet.h>6 #include <pthread.h>7 void* working(void *arg);8 //信息结构体9 struct sockinfo10 …

《数据结构、算法与应用C++语言描述》-队列的应用-图元识别问题

《数据结构、算法与应用C语言描述》-队列的应用-图元识别问题 图元识别 问题描述 数字化图像是一个 mxm 的像素矩阵。在单色图像中&#xff0c;每一个像素要么为0&#xff0c;要么为 1。值为0的像素表示图像的背景。值为1的像素表示图元上的一个点&#xff0c;称其为图元像素…

BLUE引擎变量数据分析

今天跟大家说一下BLUE引擎的变量运用&#xff0c;以及使用中的小细节。大家在使用变量的时候&#xff0c;自定义变量不要以P、G、M、I、D、N、A开头。 变量与变量之间的常用格式: SMALL M88 <$STR(G88)> ;检测私人变量M88&#xff0c;是否小于全局变量G88 LARGE M88 &l…

Rust错误处理

返回值和错误处理 panic 深入剖析 主动调用 fn main() {panic!("crash and burn"); }backtrace 栈展开 panic 时的两种终止方式 当出现 panic! 时&#xff0c;程序提供了两种方式来处理终止流程&#xff1a;栈展开和直接终止 何时该使用 panic! 先来一点背景知…

分布式定时任务xxljob

xxl-job的xxl为作者名徐雪里拼音首字母。 xxl-job的作者是2015年开始开发这个项目&#xff0c;那时候springmvcbootstrapadminlte 大行其道&#xff0c;所以这个框架调度器一直沿用这个架构。 一、运行调度器 调度器可以集群或单点运行&#xff0c;以单点运行为例 下载代码…