【C++】智能指针 学习总结 |std::shared_ptr |std::unique_ptr | std::weak_ptr

news2024/11/28 10:45:26

文章目录

  • 前言
  • 一、智能指针介绍
  • 二、普通指针和智能指针的比较案例
  • 三、std::shared_ptr
  • 四、std::unique_ptr
  • 五、std::weak_ptr
  • 六、std::shared_ptr |std::unique_ptr | std::weak_ptr三大智能指针的区别


前言

参考答案:chatgpt

一、智能指针介绍

智能指针是C++的一种对象,它可以像常规指针那样,用来指向堆上的数据。然而,智能指针的关键优势在于,**当你不再需要在堆上的数据时,它们可以自动删除。**这是通过在智能指针的析构函数中调用delete来实现的,当智能指针的生命周期结束时(例如,它离开了它的作用域),它的析构函数会被自动调用。
C++标准库提供了几种智能指针:

std::auto_ptr:这是C++98标准的一部分,但在C++11中已被弃用,C++17中已被移除。它具有所有权传递语义,意味着复制auto_ptr会改变原有auto_ptr的状态(会失去指向对象的能力)。

std::unique_ptr:这是C++11引入的智能指针,它表现出独占所有权语义,意味着在任何时间,只有一个unique_ptr可以指向给定的对象。当unique_ptr被销毁(例如,离开其作用域)时,它所指向的对象也会被delete掉。
C++14 开始,建议使用 std::make_unique 函数来创建 std::unique_ptr,

auto p = std::make_unique<MyClass>();

std::shared_ptr:这也是C++11引入的智能指针,允许多个shared_ptr指向同一个对象。shared_ptr维护了一个引用计数,当最后一个shared_ptr被销毁(离开其作用域)时,它所指向的对象才会被delete掉。

std::weak_ptr:这也是C++11引入的智能指针,它是对shared_ptr的补充和配合。weak_ptr指向一个由shared_ptr管理的对象,但不会增加该对象的引用计数。这主要用于解决shared_ptr的循环引用问题。

后面三个智能指针用的比较多。
使用智能指针可以帮助管理动态内存,避免内存泄漏。然而,虽然智能指针在许多情况下都非常有用,但它们并不能解决所有内存管理问题,例如,它们无法解决循环引用的问题(除非结合使用weak_ptr)。所以,理解并妥善使用智能指针依然非常重要。

#include < memory>,这个头文件在 C++ 中包含了许多关于内存管理的工具,包括智能指针如 std::unique_ptr 和 std::shared_ptr。


回忆一下,什么是内存泄露?
内存泄漏是指程序中已经动态分配的堆内存由于某种原因程序未释放或者无法释放,导致系统内存的浪费,降低系统性能的现象。如果程序持续运行,内存泄漏会导致系统可用内存逐渐下降,最终可能因为没有足够的内存可用而导致系统崩溃。
例如,以下是一个在 C++ 中产生内存泄漏的简单例子:

#include <iostream>

int main() {
    while (true) {
        int* p = new int[100];  // 分配内存但未释放
    }
    return 0;
}

在这个例子中,程序在一个无限循环中不断地分配内存,但从未释放它。因此,它会不断消耗系统内存,最终可能导致系统崩溃。
解决内存泄漏的方法通常涉及找出程序中没有正确释放的内存,然后修改代码以确保内存在使用后被正确释放。在一些现代编程语言中,如 Java、Python、C#、Go 等,提供了垃圾收集机制,能够自动回收不再使用的内存,从而避免内存泄漏。在 C++ 中,使用智能指针(如 std::unique_ptr 或 std::shared_ptr)也可以帮助管理内存并避免内存泄漏。

二、普通指针和智能指针的比较案例

一个原始指针代码如下:

#include <iostream>

struct MyClass {
    MyClass() { std::cout << "MyClass constructed\n"; }
    ~MyClass() { std::cout << "MyClass destroyed\n"; }
};

int main() {
    MyClass* p = new MyClass();  // 创建 MyClass 实例

    // 必须显式调用 delete 来释放内存
    delete p;

    return 0;
}

在这里插入图片描述

在上述代码中,你需要手动管理内存。你必须显式调用 delete 来释放 p 所指向的内存。如果你忘记了,那就会导致内存泄漏。

智能指针:

#include <iostream>
#include <memory>

struct MyClass {
    MyClass() { std::cout << "MyClass constructed\n"; }
    ~MyClass() { std::cout << "MyClass destroyed\n"; }
};

int main() {
    std::unique_ptr<MyClass> p(new MyClass());  // 创建 MyClass 实例

    // 不需要显式调用 delete 来释放内存
    // 当 p 离开作用域时,它的析构函数会自动被调用,释放内存

    return 0;
}

std::unique_ptr:这是一个 std::unique_ptr 模板类的实例,其中的 MyClass 是模板参数,指定了这个智能指针可以指向 MyClass 类型的对象。

p:这是我们为智能指针变量取的名字。

new MyClass():这是使用 new 关键字创建一个新的 MyClass 对象的实例,并返回其内存地址。

std::unique_ptr p(new MyClass());:整个这句代码就是创建一个名为 p 的 std::unique_ptr 对象,它将管理我们新创建的 MyClass 对象的内存。

这里的p()括号里是初始化的意思、std::unique_ptr p(new MyClass()); 中的 new MyClass() 是用来初始化 std::unique_ptr 的。这个表达式创建了一个 MyClass 类型的对象,并返回了指向这个新创建对象的指针。然后,这个指针被用来初始化 std::unique_ptr。

最后结果:
在这里插入图片描述


接下来介绍那几种关键的智能指针

三、std::shared_ptr

下面给出一种用法:

#include <iostream>
#include <memory>

struct MyClass {
    MyClass() { std::cout << "MyClass constructed\n"; }
    ~MyClass() { std::cout << "MyClass destroyed\n"; }
};

void observe(std::shared_ptr<MyClass> p) {
    std::cout << "Inside observe function: shared_ptr use_count: " << p.use_count() << "\n";
}

int main() {
    // 使用 std::make_shared 创建 shared_ptr
    std::shared_ptr<MyClass> ptr1 = std::make_shared<MyClass>();
    std::cout << "After ptr1 creation: shared_ptr use_count: " << ptr1.use_count() << "\n";
    
    {
        // 创建一个新的 shared_ptr,共享 MyClass 对象的所有权
        std::shared_ptr<MyClass> ptr2 = ptr1;
        std::cout << "After ptr2 creation: shared_ptr use_count: " << ptr1.use_count() << "\n";
        
        observe(ptr2);
    }  // ptr2 离开作用域,MyClass 对象的所有权计数减一
    std::cout << "After ptr2 destruction: shared_ptr use_count: " << ptr1.use_count() << "\n";

    // 现在只有 ptr1 拥有 MyClass 对象
}  // ptr1 离开作用域,MyClass 对象的所有权计数归零,MyClass 对象被删除

这段代码的主要目标是展示 std::shared_ptr 的用法和行为。std::shared_ptr 是一个智能指针,它允许多个指针共享同一个对象的所有权。
下面是对每一部分的详细解释:

首先定义了一个名为 MyClass 的结构体,它有一个构造函数和一个析构函数。构造函数和析构函数都会在被调用时输出一段信息。

observe 是一个函数,它接受一个 std::shared_ptr 作为参数,并输出该指针的 use_count(),即该指针当前的引用计数(共享所有权的指针数量)。

main 函数是这段代码的主要部分,展示了 std::shared_ptr 的使用:

使用 std::make_shared 创建了一个新的 std::shared_ptr,名为 ptr1,并使其指向一个新的 MyClass 实例。然后输出 ptr1 的 use_count(),此时应为 1,因为只有 ptr1 指向这个 MyClass 实例。

在一个新的作用域内(由 {} 定义),创建了一个新的 std::shared_ptr,名为 ptr2,并使其也指向 ptr1 所指向的 MyClass 实例。然后输出 ptr1 的 use_count(),此时应为 2,因为 ptr1 和 ptr2 都指向这个 MyClass 实例。

在同一作用域内,调用 observe 函数,并将 ptr2 作为参数传入。observe 函数会输出 ptr2 的 use_count(),此时也应为 2。

当这个作用域结束(即到达 }),ptr2 超出其作用域并被销毁。此时 MyClass 实例的引用计数减一。然后输出 ptr1 的 use_count(),此时应为 1,因为只剩下 ptr1 指向这个 MyClass 实例。

当 main 函数结束,ptr1 也超出其作用域并被销毁。此时 MyClass 实例的引用计数再次减一,变为 0,因此 MyClass 实例被删除,并调用其析构函数。

总的来说,这段代码展示了如何使用 std::shared_ptr 来共享一个对象的所有权,并在所有指向该对象的 std::shared_ptr 都被销毁时自动删除该对象。

最后:
在这里插入图片描述

四、std::unique_ptr

下面是一个例子:

#include <iostream>
#include <memory>

struct MyClass {
    MyClass() { std::cout << "MyClass constructed\n"; }
    ~MyClass() { std::cout << "MyClass destroyed\n"; }
};

void observe(std::unique_ptr<MyClass>& p) {
    if (p != nullptr) {
        std::cout << "Inside observe function: unique_ptr is not nullptr\n";
    } else {
        std::cout << "Inside observe function: unique_ptr is nullptr\n";
    }
}

int main() {
    // 使用 std::make_unique 创建 unique_ptr
    std::unique_ptr<MyClass> ptr1 = std::make_unique<MyClass>();
    observe(ptr1);

    // 通过 std::move 将所有权从 ptr1 转移给 ptr2
    std::unique_ptr<MyClass> ptr2 = std::move(ptr1);
    observe(ptr1);
    observe(ptr2);
}  // MyClass 对象被销毁,因为 ptr2 超出其作用域

在这段代码中,首先使用 std::make_unique 创建了一个 std::unique_ptr,名为 ptr1,并使其指向一个新的 MyClass 实例。然后调用 observe 函数来检查 ptr1 是否为 nullptr。
接下来,通过 std::move 将所有权从 ptr1 转移给新的 std::unique_ptr,名为 ptr2。此时 ptr1 变为 nullptr,因为所有权已经转移走了。
再次调用 observe 函数来检查 ptr1 和 ptr2。此时,ptr1 应为 nullptr,而 ptr2 不应为 nullptr。
当 main 函数结束时,ptr2 也超出其作用域并被销毁。此时,MyClass 实例也被删除,并调用其析构函数。这是因为 std::unique_ptr 在超出作用域时会自动删除其所指向的对象。

在这里插入图片描述

五、std::weak_ptr

std::weak_ptr 是一个用于解决 std::shared_ptr 循环引用问题的智能指针。std::weak_ptr 对于对象的所有权是无影响的,只是提供了一种访问共享对象的方式,不会增加引用计数。
以下是一个 std::weak_ptr 的使用例子:

#include <iostream>
#include <memory>

struct MyClass {
    MyClass() { std::cout << "MyClass constructed\n"; }
    ~MyClass() { std::cout << "MyClass destroyed\n"; }
};

void observe(std::weak_ptr<MyClass> p) {
    if (auto spt = p.lock()) {  // Must be copied into a shared_ptr before usage
        std::cout << "Inside observe function: weak_ptr is not expired, shared_ptr use_count: " << spt.use_count() << "\n";
    } else {
        std::cout << "Inside observe function: weak_ptr is expired\n";
    }
}

int main() {
    std::weak_ptr<MyClass> wptr;

    {
        // 使用 std::make_shared 创建 shared_ptr
        std::shared_ptr<MyClass> ptr = std::make_shared<MyClass>();
        wptr = ptr;
        observe(wptr);
    }  // ptr 离开作用域,MyClass 对象被销毁

    observe(wptr);  // wptr 已经过期,因为 MyClass 对象已经被销毁
}

在这里插入图片描述
在这段代码中,首先定义了一个 std::weak_ptr,名为 wptr。
然后在一个新的作用域内(由 {} 定义),创建了一个 std::shared_ptr,名为 ptr,并使其指向一个新的 MyClass 实例。然后将 ptr 赋值给 wptr,使 wptr 也指向 ptr 所指向的 MyClass 实例。
接着调用 observe 函数,将 wptr 作为参数传入。在 observe 函数内部,通过调用 std::weak_ptr::lock 来获取一个指向 MyClass 实例的 std::shared_ptr,并输出其 use_count()。
当这个作用域结束(即到达 }),ptr 超出其作用域并被销毁,MyClass 实例的引用计数减一,变为 0,因此 MyClass 实例被删除,并调用其析构函数。
最后,再次调用 observe 函数,将 wptr 作为参数传入。此时 wptr 已经过期,因为 MyClass 实例已经被销毁,因此 std::weak_ptr::lock 会返回一个空的 std::shared_ptr。

六、std::shared_ptr |std::unique_ptr | std::weak_ptr三大智能指针的区别

std::shared_ptr, std::unique_ptr 和 std::weak_ptr 都是 C++11 引入的智能指针,它们的主要目标是提供自动化的内存管理,以避免内存泄漏。然而,它们之间的区别主要在于如何管理内存以及所有权语义:

std::shared_ptr:这是一种引用计数的智能指针。当你将一个对象的指针赋值给一个 std::shared_ptr 时,它就会开始进行引用计数。如果有其他 std::shared_ptr 指向同一对象,引用计数就会增加。当引用计数变为0(即没有 std::shared_ptr 指向该对象时),对象就会被自动删除。

std::unique_ptr:这种智能指针保证了对象的唯一所有权语义。在任何时候,只有一个 std::unique_ptr 可以指向一个对象。当 std::unique_ptr 超出作用域或被重新赋值时,其原来指向的对象就会被自动删除。如果你需要将所有权转移给另一个 std::unique_ptr,你可以使用 std::move。

std::weak_ptr:这是一种特殊的智能指针,主要用来解决 std::shared_ptr 的循环引用问题。std::weak_ptr 本身不对对象的生命周期造成影响,也就是说,拥有一个指向对象的 std::weak_ptr 不会阻止对象被删除。它只是为了提供一种访问已经存在(并且被 std::shared_ptr 管理)的对象的方式,而不会增加引用计数。要使用 std::weak_ptr,你需要先将其升级为 std::shared_ptr,如果此时对象仍然存在,升级操作就会成功。

这些智能指针都是 C++ 标准库的一部分,设计用来自动管理内存,以避免常规指针可能导致的内存泄漏和其他问题。在编写 C++ 程序时,应优先考虑使用这些智能指针,而不是裸指针。
小插曲:
今天用vscode的时候 调试控制台不输出信息了 比如我print你好,他竟然不输出,只输出几个线程的信息,高的很是烦人,后面找到了解决方案:解决方法

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

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

相关文章

chatgpt赋能python:Python循环执行一个函数:简单而高效的代码实现

Python循环执行一个函数&#xff1a;简单而高效的代码实现 Python是一种高级编程语言&#xff0c;非常流行&#xff0c;不仅因为它易于学习和使用&#xff0c;而且因为它的灵活性。Python编程语言有很多特性&#xff0c;其中包括使用函数模块化编程&#xff0c;这在大型项目中…

图文验证码怎么测试及自动化测试怎么解决验证码问题?

目录 前言 首先&#xff0c;来简单认识下验证码 1、验证码的由来和作用 2、验证码的存储 3、验证码的原理 如何测试验证码&#xff1f; 1、手动测试 2、自动化测试 总结&#xff1a; 前言 在对安全性有要求的软件&#xff08;系统&#xff09;中都存在验证码&#xf…

高级查询 — 连接查询

关于northwind 示例数据库 查询数据库中的表 show tables;查询表的表属性 desc xxx(表名);连接查询 1.概述 若一个查询同时涉及两个及以上的表&#xff0c;则称之为连接查询。也可以叫做多表查询。使用join关键字进行多表连接。 2.分类&#xff08;按功能&#xff09; 内连…

二阶低通滤波器(通用滤波器)

一阶低通滤波器的详细算法介绍请参看下面文章: 一阶RC算法公式和梯形图代码 PLC信号处理系列之一阶低通(RC)滤波器算法_反馈信号低通滤波器_RXXW_Dor的博客-CSDN博客1、先看看RC滤波的优缺点 优点:采用数字滤波算法来实现动态的RC滤波,则能很好的克服模拟滤波器的缺点; 1…

学院课程功能使用说明书

引子 越来越多的讲师入驻csdn&#xff0c;也有很多学员通过学院平台进行课程学习&#xff0c;我们收到越来越多用户的反馈&#xff0c;现在通过本帖子来对课程功能做一个介绍&#xff0c;简称课程功能使用说明书1.0版&#xff0c;有任何问题均可在帖子下留言。 讲师 如何成为…

Centos7部署项目

Centos7部署项目 Centos7部署项目Centos7安装gitCentos7从github拉取代码配置git配置用于提交代码的用户名配置用户邮箱生成公钥效果如下 github添加SSH key打开你的GitHub获取ssh key公钥内容验证 拉取项目新建空白文件夹拉取项目 使用Docker Compose创建项目容器创建docker-c…

DC LAB8SDC约束四种时序路径分析

DC LAB 1.启动DC2.读入设计3. 查看所有违例的约束报告3.1 report_constraint -all_violators (alias rc)3.2 view report_constraint -all_violators -verbose -significant_digits 4 (打印详细报告) 4.查看时序报告 report_timing -significant_digits 45. 约束组合逻辑(adr_i…

1句代码,挽回1000万损失

前言&#xff1a; 在企业中&#xff0c;出于数据安全和应用高可用&#xff0c;很多软件和企业会对工程文件、数据库等做自动备份和应用容灾等。一份数据或者文件会保留到很多地方&#xff0c;虽然满足了安全性的需求&#xff0c;但是会因为保存数据区间太久造成占用大量的存储成…

chatgpt赋能python:Python快速入门神器:入门教程

Python快速入门神器&#xff1a;入门教程 Python作为一种高级的编程语言&#xff0c;在近年来逐渐成为编程语言领域里面的一匹黑马&#xff0c;在数据科学、人工智能、自动化测试和WEB开发等领域都得到了广泛的应用。Python极易入门&#xff0c;使用简单&#xff0c;代码质量较…

UnityVR--组件8--Avatar骨骼映射遮罩

什么是Avatar 模型中的人物都具有结构相似的骨架&#xff0c;如果建立一个模型骨架结构的映射&#xff0c;就能将同一个动画应用于不同的模型中&#xff0c;这就是Avatar&#xff0c;实现了一种动画重定向的功能。一般使用3DMax、Maya制作并导出为.fbx的人物模型&#xff0c;在…

【MySQL】MySQL在Linux中的环境安装与基本使用

目录 一、MySQL环境的安装 1、MySQL环境安装 2、安装MySQL出现的问题 3、登录MySQl 3.1方案一 3.2方案二 4、修改MySQL配置文件 5、可选设置&#xff1a;开机自启动MySQL&#xff08;云服务器没事一般不会关机&#xff09; 二、MySQL数据库基础 1、一些概念 2、基本…

Java中进制转换的两种方法你知道吗?

目录 十进制转其他进制 其他进制转十进制 实战&#xff1a; A进制转B进制 关于大数运算可以参考躲不掉的高精度计算&#xff0c;蓝桥杯必考_高精度算法在哪些比赛考_无忧#的博客-CSDN博客 十进制转其他进制 使用 Integer.toString(int n,int radix) 方法&#xff0c;该方法…

【LeetCode】739. 每日温度

739. 每日温度&#xff08;中等&#xff09; 思路 我们可以维持一个单调递减的栈&#xff0c;表示每天的温度&#xff1b;为了方便计算天数差&#xff0c;这里存放位置&#xff08;下标&#xff09;&#xff0c;而非温度本身。因为温度可以通过访问数组下标获取。 从左向右遍历…

书接上文,基于藏文手写数字数据开发构建yolov5n轻量级藏文手写数字检测识别系统

在上一篇文章中&#xff1a;《python基于轻量级CNN模型开发构建手写藏文数字识别系统》 开发实现了轻量级的藏文手写数字识别系统&#xff0c;这里主要是想基于前文的数据&#xff0c;整合目标检测模型来进一步挖掘藏文手写数字数据集的可玩性&#xff0c;基于yolov5n开发构建…

Android kotlin序列化之@Parcelize详解与使用

一、介绍 在Android开发过程中&#xff0c;序列化使用概率一直很高。在页面之间传递的对象&#xff0c;需要要使用序列化&#xff0c;常见的序列化&#xff1a;Parcelable、Serialization。 由于Parcelable在传递压缩比高&#xff0c;效率高&#xff0c;一直被Google官方推荐。…

可调电阻器

1、可调电阻&#xff08;Trimming Potentiometer&#xff0c;Variable Resistor&#xff09; 1.1、电气特性&#xff08;Electrical Characteristics&#xff09; ItemSpec ExampleDescriptionTotal Resistance&#xff08;TR&#xff09;&#xff0c;总和阻抗200K端子1和3之间…

独立按键检测短按、长按,松手后响应操作

背景 有项目使用独立按键检测&#xff0c;短按、长按。根据使用效果&#xff0c;发现松手后&#xff0c;也就是按键弹起后响应操作比较好操作。 记得之前&#xff0c;博主写过一篇关于按键的检测的文章&#xff0c;但是过于复杂了。可能很难懂&#xff0c;这里就简单一点&…

Flask学习-环境配置

目录 一.环境部署 二.Flask基本结构 三.完整代码 四.运行效果 一.环境部署 在安装好python&#xff0c;pip环境的基础上在命令行输入如下指令&#xff1a; pip install flask flask框架即安装完毕。 二.Flask基本结构 flask的使用通过创建实例实现。创建方法如下&…

ArgoCD(五)----ArgoCD 各CRD资源配置文件规范

3.4.1 Application资源规范 Application CRD的spec字段主要嵌套如下几个字段&#xff1a; source &#xff1a;配置仓库及相关的配置访问及使用方法&#xff1b;支持如下几种类型&#xff1a; kubernetets的原生配置文件Helm chartkustomize&#xff1a;由kustomize字段进行定义…

软件测试实战,支付二维码测试-测试点汇总,全面覆盖...

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 面试的时候&#…