C++关于智能指针的一些常见问题

首先解释指针和引用在C++中都用于间接访问变量,但它们有一些区别:

1. 指针是一个变量,它保存了另一个变量得内存地址;引用是另一个变量的别名,与原变量共享内存地址。

2. 指针可以被重新赋值,指向不同的变量;引用在初始化后不能更改,始终指向同一个变量。

3. 指针可以为nullptr,表示不指向任何变量;引用必须绑定到一个变量,不能为nullptr。

4. 使用指针需要对其进行解引用以获取或修改其指向的变量的值;引用可以直接使用,无需解引用。

在汇编层面来看    引用会被C++编译器当做const指针来进行操作。

RAII(Resource Acquisition Is Initialization)是由C++之父Bjarne Stroustrup提出的,中文翻译为资源获取即初始化,他说:使用局部对象来管理资源的技术称为资源获取即初始化;

这里的资源主要是指操作系统中有限的东西如指针内存、网络套接字、文件等等,局部对象是指存储在栈的对象,它的生命周期是由操作系统来管理的,无需人工介入。

C/C++常见的内存错误

在C/C++中,使用指针会出现各种问题,比如:

  1. 野指针:未初始化或已经被释放的指针被称为野指针
  2. 空指针:指向空地址的指针被称为空指针
  3. 内存泄露:如果在使用完动态分配的内存后忘记释放,就会造成内存泄露,长时间运行的程序可能会消耗大量内存
  4. 悬空指针:指向已经释放的内存的指针被称为悬空指针
  5. 内存泄露和悬空指针的混合:在一些情况下,由于内存泄露和悬空指针共同存在,程序可能会出现异常行为

智能指针

智能指针是一种可以自动管理内存的指针,它可以在不需要手动释放内存的情况下,确保对象正确地销毁。

这种指针可以显著降低程序中的内存泄露和悬空指针的风险。

智能指针的核心思想就是RAII

在C++中主要有两种智能指针

std::unique_ptr

std::shared_ptr

std::unique_ptr

std::unique_ptr是一个独占所有权的智能指针,它保证指向的内存只能有一个unique_ptr拥有,不能共享所有权。

std::shared_ptr

std::shared_ptr是一个共享所有权的智能指针,它允许多个shared_ptr指向同一个对象,当最后一个shared_ptr超出超出作用域时,所指向的内存才会被自动释放。

shared_ptr通过引用计数来记录有多少个shared_ptr共享同一个对象。

#include <iostream>
#include <memory>class MyClass {
public:MyClass() { std::cout << "MyClass 构造函数\n"; }~MyClass() { std::cout << "MyClass 析构函数\n"; }void do_something() { std::cout << "MyClass::do_something() 被调用\n"; }
};int main() {{std::shared_ptr<MyClass> ptr1 = std::make_shared<MyClass>();{std::shared_ptr<MyClass> ptr2 = ptr1; // 这里共享 MyClass 对象的所有权ptr1->do_something();ptr2->do_something();std::cout << "ptr1 和 ptr2 作用域结束前的引用计数: " << ptr1.use_count() << std::endl;} // 这里 ptr2 被销毁,但是 MyClass 对象不会被删除,因为 ptr1 仍然拥有它的所有权std::cout << "ptr1 作用域结束前的引用计数: " << ptr1.use_count() << std::endl;} // 这里 ptr1 被销毁,同时 MyClass 对象也会被删除,因为它是最后一个拥有对象所有权的 shared_ptrreturn 0;
}
MyClass 构造函数
MyClass::do_something() 被调用
MyClass::do_something() 被调用
ptr1 和 ptr2 作用域结束前的引用计数: 2
ptr1 作用域结束前的引用计数: 1
MyClass 析构函数

 

shared_ptr的double free问题

double free 问题就是一块内存空间或者资源被释放两次。那么为什么会释放两次呢?

double free 可能是下面这些原因造成的:

  • 直接使用原始指针创建多个 shared_ptr,而没有使用 shared_ptr 的 make_shared 工厂函数,从而导致多个独立的引用计数。
  • 循环引用,即两个或多个 shared_ptr 互相引用,导致引用计数永远无法降为零,从而无法释放内存。


解决 shared_ptr double free 问题的方法:

  • 使用 make_shared 函数创建 shared_ptr 实例,而不是直接使用原始指针。这样可以确保所有 shared_ptr 实例共享相同的引用计数。
  • 对于可能产生循环引用的情况,使用 weak_ptr。weak_ptr 是一种不控制对象生命周期的智能指针,它只观察对象,而不增加引用计数。这可以避免循环引用导致的内存泄漏问题。

shared_ptr 常用 API

shared_ptr<T> 构造函数:创建一个空的 shared_ptr,不指向任何对象。

std::shared_ptr<int> ptr;


make_shared<T>(args...):创建一个 shared_ptr,并在单次内存分配中同时创建对象和控制块。这比直接使用 shared_ptr 的构造函数要高效。

std::shared_ptr<int> ptr = std::make_shared<int>(42);


reset():释放当前 shared_ptr 的所有权,将其设置为 nullptr。如果当前 shared_ptr 是最后一个拥有对象所有权的智能指针,则会删除对象。

ptr.reset();


reset(T*):释放当前 shared_ptr 的所有权,并使其指向新的对象。如果当前 shared_ptr 是最后一个拥有对象所有权的智能指针,则会删除原对象。

ptr.reset(new int(42));


get():返回指向的对象的裸指针。注意,这个裸指针的生命周期由 shared_ptr 管理,你不应该使用它来创建另一个智能指针。

int* raw_ptr = ptr.get();


operator* 和 operator->:访问指向的对象。

int value = *ptr;
std::shared_ptr<std::vector<int>> vec_ptr = std::make_shared<std::vector<int>>();
vec_ptr->push_back(42);


use_count():返回当前 shared_ptr 的引用计数,即有多少个 shared_ptr 共享同一个对象。注意,use_count() 通常用于调试,不应该用于程序逻辑。

size_t count = ptr.use_count();


unique():检查当前 shared_ptr 是否是唯一拥有对象所有权的智能指针。等价于 use_count() == 1。

bool is_unique = ptr.unique();


swap(shared_ptr&):交换两个 shared_ptr 的内容。

std::shared_ptr<int> ptr1 = std::make_shared<int>(42);
std::shared_ptr<int> ptr2 = std::make_shared<int>(24);
ptr1.swap(ptr2);


operator bool():将 shared_ptr 隐式转换为 bool 类型,用于检查其是否为空。

if (ptr) {std::cout << "ptr 不为空" << std::endl;
} else {std::cout << "ptr 为空" << std::endl;
}

文章参考
作者: 编程指北
链接: https://csguide.cn/cpp/memory/shared_ptr.html#shared-ptr-%E7%9A%84%E4%BD%BF%E7%94%A8
来源: https://csguide.cn

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

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

相关文章

JVM篇--JVM调优高频面试题

1 说一下 JVM 调优的工具&#xff1f; JDK 自带了很多监控工具&#xff0c;都位于 JDK 的 bin 目录下&#xff0c;其中最常用的是jconsole 和 jvisualvm 这两款视图监控工具。 jconsole&#xff1a;用于对 JVM 中的内存、线程和类等进行监控&#xff1b; jvisualvm&#xff1a…

将AWS iot消息数据发送Kinesis Firehose Stream存向S3

观看此文章之前&#xff0c;请先学习AWS iot的数据收集&#xff1a; 使用Linux SDK客户端向AWS Iot发送数据-CSDN博客 1、工作原理&#xff1a; 1.1 规则 规则可让您的设备与 AWS 服务进行交互。分析规则并根据物品发送的消息执行操作。您可以使用规则来支持任务&#xff0…

C++类和对象(上)

✨Blog&#xff1a;&#x1f970;不会敲代码的小张:)&#x1f970; &#x1f251;推荐专栏&#xff1a;C语言&#x1f92a;、Cpp&#x1f636;‍&#x1f32b;️、数据结构初阶&#x1f480; &#x1f4bd;座右铭&#xff1a;“記住&#xff0c;每一天都是一個新的開始&#x1…

字符串的方法有哪些?

JavaScript中字符串作为基本类型&#xff0c;它为我们提供了很多好用的API&#xff0c;本期就来总结一下一些常见的字符串方法吧 字符串的方法按照功能来划分&#xff0c;可分为增删改查四个部分 写在前面&#xff1a;了解一个方法&#xff0c;我们可以通过三个方面&#xff…

(2)Elastix图像配准:参数文件(配准精度的关键)

文章目录 前言一、Elastix简介二、参数文件&#xff08;类型&#xff09;三、参数文件&#xff08;定义&#xff09;&#xff1a;由多个组件组成&#xff0c;每个组件包含多个参数。3.1、组件的相关参数3.2、图解组件3.2.1、图解 - 金字塔&#xff08;pyramid&#xff09;3.2.2…

从零开始训练 YOLOv8最新8.1版本教程说明(包含Mac、Windows、Linux端 )同之前的项目版本代码有区别

从零开始训练 YOLOv8 - 最新8.1版本教程说明 本文适用Windows/Linux/Mac:从零开始使用Windows/Linux/Mac训练 YOLOv8 算法项目 《芒果 YOLOv8 目标检测算法 改进》 适用于芒果专栏改进 YOLOv8 算法 文章目录 官方 YOLOv8 算法第一步 配置环境1.1 系列配置1.2 代码执行第二步…

wpf控件Expander集合下的像素滚动

项目场景&#xff1a;Expander集合滚动 如下图&#xff0c;有一个Expander集合&#xff0c;且设置 ScrollViewer.VerticalScrollBarVisibility "Auto" 每个Expaner下包含有若干元素&#xff0c;当打开Expader(即IsExpanded "true"&#xff09;时&#…

网络通讯接口RS232与RS485含义与区别

在现代化数据中心机房中&#xff0c;无论是前期设计、现场调试还是后期维护&#xff0c;通常都要了解通信协议&#xff0c;如机房供电设备、环境设备、空调设备等综合集中监控。通信协议是指双方实体完成通信或服务所必须遵循的规则和约定。协议定义了数据单元使用的格式&#…

Leetcode刷题(二十九)

两数相除&#xff08;Medium&#xff09; 给你两个整数&#xff0c;被除数 dividend 和除数 divisor。将两数相除&#xff0c;要求 不使用 乘法、除法和取余运算。整数除法应该向零截断&#xff0c;也就是截去&#xff08;truncate&#xff09;其小数部分。例如&#xff0c;8.…

C#,最小生成树(MST)博鲁夫卡(Boruvka)算法的源代码

Otakar Boruvka 本文给出Boruvka算法的C#实现源代码。 Boruvka算法用于查找边加权图的最小生成树&#xff08;MST&#xff09;&#xff0c;它早于Prim和Kruskal的算法&#xff0c;但仍然可以被认为是两者的关联。 一、Boruvka算法的历史 1926年&#xff0c;奥塔卡博鲁夫卡&…

redis 分布式锁的原理

Redis 分布式锁是通过利用 Redis 的原子操作和特性来实现的。下面是 Redis 分布式锁的基本原理&#xff1a; 获取锁&#xff1a;当一个进程或线程需要获取锁时&#xff0c;它会通过执行 Redis 命令&#xff08;例如 SETNX&#xff09;在 Redis 中尝试设置一个特定的键作为锁。如…

QQ云端机器人登录系统php源码

这款源码主要是针对群机器人爱好者的&#xff0c;这是一个通过对接挂机宝里面机器人框架的一个网页站点&#xff0c;用户通过网页登录 QQ 账号至挂机宝里面框架&#xff08;可扫码登录、账密登录、跳转 QQ 快捷登录&#xff09;&#xff0c;无需通过机器人即可实现登录&#xf…

《设计模式的艺术》笔记 - 观察者模式

介绍 观察者模式定义对象之间的一种一对多依赖关系&#xff0c;使得每当一个对象状态发生改变时&#xff0c;其相关依赖对象皆得到通知并被自动更新。 实现 myclass.h // // Created by yuwp on 2024/1/12. //#ifndef DESIGNPATTERNS_MYCLASS_H #define DESIGNPATTERNS_MYCLA…

Oracle Linux 6.10 安装图解

风险告知 本人及本篇博文不为任何人及任何行为的任何风险承担责任&#xff0c;图解仅供参考&#xff0c;请悉知&#xff01;本次安装图解是在一个全新的演示环境下进行的&#xff0c;演示环境中没有任何有价值的数据&#xff0c;但这并不代表摆在你面前的环境也是如此。生产环境…

ZK高可用架构涉及常用功能整理

ZK高可用架构涉及常用功能整理 1. zk的高可用系统架构和相关组件1.1 Quorum机制1.2 ZAB协议 2. zk的核心参数2.1 常规配置2.2 特殊优化配置 3. zk常用命令3.1 常用基础命令3.2 常用运维命令 4. 事务性4.1 数据写流程4.2 数据读流程 5. 疑问和思考5.1 zk不擅长处理哪些场景&…

gitlab runner 安装、注册、配置、使用

天行健&#xff0c;君子以自强不息&#xff1b;地势坤&#xff0c;君子以厚德载物。 每个人都有惰性&#xff0c;但不断学习是好好生活的根本&#xff0c;共勉&#xff01; 文章均为学习整理笔记&#xff0c;分享记录为主&#xff0c;如有错误请指正&#xff0c;共同学习进步。…

「一本通 3.6 例 1」分离的路径

题目描述 为了从 F F F 个草场中的一个走到另一个&#xff0c;贝茜和她的同伴们不得不路过一些她们讨厌的可怕的树。奶牛们已经厌倦了被迫走某一条路&#xff0c;所以她们想建一些新路&#xff0c;使每一对草场之间都会至少有两条相互分离的路径&#xff0c;这样她们就有多一…

vue常用指令(v-show)

一、v-show 指令 作用: 根据真假值,切换元素的显示状态 二、代码演示 1、v-show 绑定判断条件后&#xff0c;根据布尔值决定是否显示图片 不显示图片 代码 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8" /><…

骨传导是哪个意思?骨传导的应用领域在哪

很多人都问我骨传导是哪个意思&#xff1f;骨传导的应用领域在哪&#xff1f;我们都知道声音能够在固体、液体、空气三种介质中传播。通常情况下&#xff0c;声音进入我们内耳的方式主要有两种&#xff1a;空气传导和骨传导&#xff0c;骨传导也就是通过固体传导。我们比较熟悉…

深度学习——pycharm远程连接

目录 远程环境配置本地环境配置&#xff08;注意看假设&#xff01;&#xff01;!这是很多博客里没写的&#xff09;步骤1步骤2步骤2.1 配置Connection步骤2.2 配置Mappings 步骤3 配置本地项目的远程解释器技巧1 pycharm中远程终端连接技巧2 远程目录技巧3 上传代码文件技巧4 …