shared_ptr 智能指针

shared_ptr 智能指针

文章目录

  • shared_ptr 智能指针
    • 为什么要引入智能指针
    • 智能指针的作用
    • C++ 中的智能指针种类
    • std::shared_ptr
      • 使用 `std::make_shared` 创建 `shared_ptr`(推荐方式)
      • 使用 `shared_ptr` 的拷贝或移动构造
      • 通过构造函数初始化
      • 通过reset方法初始化
      • 获取原始指针
    • 指定删除器
    • 使用 `std::shared_ptr` 的注意事项
      • 循环引用
      • 使用 `std::weak_ptr` 解决循环引用

为什么要引入智能指针

在C++中没有垃圾回收机制,必须自己释放分配的内存,否则就会造成内存泄露。解决这个问题最有效的方法是使用智能指针(smart pointer)。智能指针是存储指向动态分配(堆)对象指针的类,用于生存期的控制,能够确保在离开指针所在作用域时,自动地销毁动态分配的对象,防止内存泄露。智能指针的核心实现技术是引用计数,每使用它一次,内部引用计数加1,每析构一次内部的引用计数减1,减为0时,删除所指向的堆内存。

智能指针的作用

  1. 自动管理动态内存:智能指针通过 RAII(资源获取即初始化)机制自动释放内存。RAII 是 C++ 的核心设计理念之一,资源的生命周期与对象的生命周期绑定,从而确保资源不会被泄漏。
  2. 避免内存泄漏:智能指针通过引用计数或其他机制,确保内存能够在不再使用时自动释放。
  3. 避免悬挂指针和野指针:智能指针能够在其不再使用时自动销毁,避免了手动删除后指针仍然存在的危险。
  4. 确保异常安全:智能指针通过作用域控制内存,确保即使发生异常,也能正确释放资源。
  5. 简化代码:相比传统指针手动管理内存,智能指针大大简化了内存管理代码,提升了程序的可维护性和可靠性。

C++ 中的智能指针种类

  1. std::unique_ptr
  • 用途:表示独占所有权的智能指针,意味着一个 unique_ptr 只能拥有一个对象的所有权。不能被复制,只能移动。
  • 特点
    • 独占所有权:同一个对象只能有一个 unique_ptr 管理,避免了多重释放的问题。
    • 不可拷贝:不能通过拷贝构造函数或赋值操作符来复制,确保了对象的唯一所有权。
    • 可以转移所有权:可以通过 std::moveunique_ptr 的所有权从一个指针转移到另一个。
  • 典型用法
std::unique_ptr<int> ptr1 = std::make_unique<int>(10);
std::unique_ptr<int> ptr2 = std::move(ptr1);  // 转移所有权
  1. std::shared_ptr
  • 用途:表示共享所有权的智能指针,多个 shared_ptr 可以共同拥有同一个对象的所有权。
  • 特点
    • 引用计数shared_ptr 使用引用计数的机制来跟踪有多少个 shared_ptr 指向同一对象。当最后一个指向对象的 shared_ptr 被销毁时,内存会自动释放。
    • 可以拷贝和赋值shared_ptr 可以通过拷贝构造和赋值操作符进行拷贝,因此它适用于多个地方共享资源的情况。
    • 线程安全shared_ptr 在修改引用计数时是线程安全的,但对象本身的操作仍需要额外的同步机制。
  • 典型用法
std::shared_ptr<int> ptr1 = std::make_shared<int>(10);
std::shared_ptr<int> ptr2 = ptr1;  // 引用计数增加,两个指针共享所有权
  1. std::weak_ptr
  • 用途:用于解决 shared_ptr 引用计数环的问题。weak_ptr 并不增加引用计数,只是观察 shared_ptr 指向的对象。
  • 特点
    • 不增加引用计数weak_ptr 不会增加 shared_ptr 的引用计数,因此不会影响对象的生命周期。
    • **可以转换为 **shared_ptr:如果对象仍然存在,weak_ptr 可以通过 lock() 方法转换为 shared_ptr,从而访问对象。
    • 防止循环引用:在 shared_ptr 之间形成循环引用时,weak_ptr 可以用来打破这种循环,避免内存泄漏。
  • 典型用法
std::shared_ptr<int> ptr1 = std::make_shared<int>(10);
std::weak_ptr<int> weakPtr = ptr1;  // 不增加引用计数
if (auto ptr2 = weakPtr.lock()) {   // 如果对象还存在,获得一个 shared_ptr
    // 使用 ptr2
}

std::shared_ptr

使用 std::make_shared 创建 shared_ptr(推荐方式)

std::make_shared 是创建 std::shared_ptr 的推荐方法,它同时分配 shared_ptr 和对象的内存,能够提高效率并减少内存分配次数。

使用std::make_shared()模板函数可以完成内存地址的创建,并将最终得到的内存地址传递给共享智能指针对象管理。如果申请的内存是普通类型,通过函数的()可完成地址的初始化,如果要创建一个类对象,函数的()内部需要指定构造对象需要的参数,也就是类构造函数的参数。

#include <iostream>
#include <memory>

class MyClass {
public:
    MyClass() { std::cout << "MyClass constructor\n"; }
    ~MyClass() { std::cout << "MyClass destructor\n"; }
    void say_hello() { std::cout << "Hello, World!\n"; }
};

int main() {
    // 使用 std::make_shared 创建 shared_ptr
    std::shared_ptr<MyClass> p1 = std::make_shared<MyClass>();

    p1->say_hello(); // 调用成员函数

    // 不需要手动释放内存,shared_ptr 会自动销毁对象
    return 0;
}
----- 输出 -----
MyClass constructor
Hello, World!
MyClass destructor

使用 shared_ptr 的拷贝或移动构造

如果已有一个 shared_ptr,可以通过拷贝或移动构造一个新的 shared_ptr,共享同一个对象。

#include <iostream>
#include <memory>

class MyClass {
public:
    MyClass() { std::cout << "MyClass constructor\n"; }
    ~MyClass() { std::cout << "MyClass destructor\n"; }
    void say_hello() { std::cout << "Hello, World!\n"; }
};

int main() {
    // 创建 shared_ptr
    std::shared_ptr<MyClass> p1 = std::make_shared<MyClass>();

    // 拷贝构造
    std::shared_ptr<MyClass> p2 = p1;

    // 移动构造
    std::shared_ptr<MyClass> p3 = std::move(p1);

    p2->say_hello();
    p3->say_hello(); // p1 已经为空,p3 拥有资源

    return 0;
}

通过构造函数初始化

// shared_ptr<T> 类模板中,提供了多种实用的构造函数, 语法格式如下:
std::shared_ptr<T> 智能指针名字(创建堆内存);

// 管理当前对象的 shared_ptr 实例数量,或若无被管理对象则为 0。
long use_count() const noexcept;
#include <iostream>
#include <memory>
using namespace std;

int main()
{
    // 使用智能指针管理一块 int 型的堆内存
    shared_ptr<int> ptr1(new int(520));
    cout << "ptr1管理的内存引用计数: " << ptr1.use_count() << endl;
    // 使用智能指针管理一块字符数组对应的堆内存
    shared_ptr<char> ptr2(new char[12]);
    cout << "ptr2管理的内存引用计数: " << ptr2.use_count() << endl;
    // 创建智能指针对象, 不管理任何内存
    shared_ptr<int> ptr3;
    cout << "ptr3管理的内存引用计数: " << ptr3.use_count() << endl;
    // 创建智能指针对象, 初始化为空
    shared_ptr<int> ptr4(nullptr);
    cout << "ptr4管理的内存引用计数: " << ptr4.use_count() << endl;

    return 0;
}
----- 输出:
ptr1管理的内存引用计数: 1
ptr2管理的内存引用计数: 1
ptr3管理的内存引用计数: 0
ptr4管理的内存引用计数: 0

如果智能指针被初始化了一块有效内存,那么这块内存的引用计数+1,如果智能指针没有被初始化或者被初始化为nullptr空指针,引用计数不会+1。另外,不要使用一个原始指针初始化多个shared_ptr。

int *p = new int;
shared_ptr<int> p1(p);
shared_ptr<int> p2(p);      // error, 编译不会报错, 运行会出错

通过reset方法初始化

共享智能指针类提供的std::shared_ptr::reset方法函数原型如下:

void reset() noexcept;

template< class Y >
void reset( Y* ptr );

template< class Y, class Deleter >
void reset( Y* ptr, Deleter d );

template< class Y, class Deleter, class Alloc >
void reset( Y* ptr, Deleter d, Alloc alloc );

  1. ptr:指向要取得所有权的对象的指针。调用此 reset 后,会接管这个裸指针的所有权,原本的管理对象会被删除。
  2. d: 删除器用于删除 ptr 指向的对象。允许你在重置时自定义对象的销毁方式。
  3. Alloc : 是一个自定义分配器,它控制内存的分配和释放,通常用于自定义内存管理场景。
#include <iostream>
#include <string>
#include <memory>
using namespace std;

int main()
{
    // 使用智能指针管理一块 int 型的堆内存, 内部引用计数为 1
    shared_ptr<int> ptr1 = make_shared<int>(520);
    shared_ptr<int> ptr2 = ptr1;
    shared_ptr<int> ptr3 = ptr1;
    shared_ptr<int> ptr4 = ptr1;
    cout << "ptr1管理的内存引用计数: " << ptr1.use_count() << endl;
    cout << "ptr2管理的内存引用计数: " << ptr2.use_count() << endl;
    cout << "ptr3管理的内存引用计数: " << ptr3.use_count() << endl;
    cout << "ptr4管理的内存引用计数: " << ptr4.use_count() << endl;

    ptr4.reset();
    cout << "ptr1管理的内存引用计数: " << ptr1.use_count() << endl;
    cout << "ptr2管理的内存引用计数: " << ptr2.use_count() << endl;
    cout << "ptr3管理的内存引用计数: " << ptr3.use_count() << endl;
    cout << "ptr4管理的内存引用计数: " << ptr4.use_count() << endl;

    shared_ptr<int> ptr5;
    ptr5.reset(new int(250));
    cout << "ptr5管理的内存引用计数: " << ptr5.use_count() << endl;

    return 0;
}

-----输出:
ptr1管理的内存引用计数: 4
ptr2管理的内存引用计数: 4
ptr3管理的内存引用计数: 4
ptr4管理的内存引用计数: 4

ptr1管理的内存引用计数: 3
ptr2管理的内存引用计数: 3
ptr3管理的内存引用计数: 3
ptr4管理的内存引用计数: 0

ptr5管理的内存引用计数: 1

对于一个未初始化的共享智能指针,可以通过reset方法来初始化,当智能指针中有值的时候,调用reset会使引用计数减1。

获取原始指针

通过智能指针可以管理一个普通变量或者对象的地址,此时原始地址就不可见了。当我们想要修改变量或者对象中的值的时候,就需要从智能指针对象中先取出数据的原始内存的地址再操作,解决方案是调用共享智能指针类提供的get()方法,其函数原型如下:

T* get() const noexcept;

指定删除器

std::shared_ptr 允许使用指定的删除器(Deleter)来定制对象销毁的方式。删除器是一个函数对象、函数指针或任何具有 operator() 的对象,可以在 shared_ptr 被销毁时,执行自定义的删除操作。

原型如下:

std::shared_ptr<T> ptr(p, Deleter deleter);
1 > p 是裸指针,shared_ptr 将负责管理它的生命周期。
2 > Deleter 是一个删除器,它在 shared_ptr 被销毁时被调用,用来销毁管理的对象。

使用:

#include <iostream>
#include <memory>

void custom_deleter(int* ptr) {
    std::cout << "Custom deleter deleting pointer: " << *ptr << std::endl;
    delete ptr;  // 使用 delete 进行销毁
}

int main() {
    // 创建一个 shared_ptr,传入裸指针和自定义删除器
    std::shared_ptr<int> ptr(new int(42), custom_deleter);

    // 使用 ptr 进行操作
    std::cout << "Shared pointer holds: " << *ptr << std::endl;

    /*
    // 使用 lambda 表达式作为删除器
    auto deleter = [](int* ptr) {
        std::cout << "Lambda deleter deleting pointer: " << *ptr << std::endl;
        delete ptr;
    };

    std::shared_ptr<int> ptr(new int(100), deleter);

    std::cout << "Shared pointer holds: " << *ptr << std::endl;
    */
    // 当 ptr 超出作用域时,custom_deleter 会被调用
    return 0;
}
-----  输出 ----
Shared pointer holds: 42
Custom deleter deleting pointer: 42

使用 std::shared_ptr 的注意事项

循环引用

std::shared_ptr 会通过引用计数来管理对象的生命周期,如果两个 shared_ptr 相互持有对方,形成循环引用,那么它们的引用计数永远不会减少,导致内存泄漏。

#include <iostream>
#include <memory>

class A; // 前向声明

class B {
public:
    std::shared_ptr<A> ptrA;
    B() { std::cout << "B created\n"; }
    ~B() { std::cout << "B destroyed\n"; }
};

class A {
public:
    std::shared_ptr<B> ptrB;
    A() { std::cout << "A created\n"; }
    ~A() { std::cout << "A destroyed\n"; }
};

int main() {
    std::shared_ptr<A> pA = std::make_shared<A>();
    std::shared_ptr<B> pB = std::make_shared<B>();

    pA->ptrB = pB;
    pB->ptrA = pA;

    // pA 和 pB 互相持有对方,导致循环引用,内存不会被释放
    return 0;
}

这个程序不会输出 “A destroyed” 和 “B destroyed”。这是因为 pA 和 pB 互相持有对方,引用计数永远不会降到 0,导致内存泄漏。

使用 std::weak_ptr 解决循环引用

为了避免循环引用,可以使用 std::weak_ptr。std::weak_ptr 不增加引用计数,它允许 shared_ptr 相互引用,但不会影响对象的生命周期。~

#include <iostream>
#include <memory>

class A; // 前向声明

class B {
public:
    std::weak_ptr<A> ptrA;  // 使用 weak_ptr
    B() { std::cout << "B created\n"; }
    ~B() { std::cout << "B destroyed\n"; }
};

class A {
public:
    std::shared_ptr<B> ptrB;
    A() { std::cout << "A created\n"; }
    ~A() { std::cout << "A destroyed\n"; }
};

int main() {
    std::shared_ptr<A> pA = std::make_shared<A>();
    std::shared_ptr<B> pB = std::make_shared<B>();

    pA->ptrB = pB;
    pB->ptrA = pA;

    // 使用 weak_ptr 解决循环引用
    return 0;
}
----------------输出
A created
B created
A destroyed
B destroyed

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

构建全面的生产监控体系:从基础设施到业务服务

在现代 IT 系统中&#xff0c;监控体系是确保高可用性、高性能和稳定性的核心工具。一个完善的监控体系能够及时发现系统问题、分析问题根源并快速采取应对措施&#xff0c;避免故障进一步扩散。本文将从基础设施层、中间件层、容器与编排层、应用与服务层逐步展开&#xff0c;…

Rk3588 FFmpeg 拉流 RTSP, 硬解码转RGB

RK3588 ,基于FFmpeg, 拉取RTSP,使用 h264_rkmpp 实现硬解码. ⚡️ 传送 ➡️ RK3588, FFmpeg 拉流 RTSP, mpp 硬解码转RGBRk3588 FFmpeg 拉流 RTSP, 硬解码转RGBUbuntu x64 架构, 交叉编译aarch64 FFmpeg mppRK3588 , mpp硬编码rgb, 保存MP4视频文件.</

进程通信方式---共享映射区(无血缘关系用的)

5.共享映射区&#xff08;无血缘关系用的&#xff09; 文章目录 5.共享映射区&#xff08;无血缘关系用的&#xff09;1.概述2.mmap&&munmap函数3.mmap注意事项4.mmap实现进程通信父子进程练习 无血缘关系 5.mmap匿名映射区 1.概述 原理&#xff1a;共享映射区是将文件…

【Redis篇】Set和Zset 有序集合基本使用

目录 Set 基本命令 sadd SMEMBERS SISMEMBER SCARD 返回值&#xff1a; SPOP SMOVE SREM 集合间操作 交集&#xff1a; 并集&#xff1a; 差集&#xff1a; ​编辑 内部编码 使用场景&#xff1a; Zset 有序集合 Zset基本命令 ZADD ZCARD ZCOUNT ZRANGE …

SAP自定义权限对象

一、创建域和数据元素 SE11 二、创建权限字段 SU20 关联数据元素ZAPP 三、创建权限对象 SU21 关联权限字段ZAPP 四、新建程序&#xff0c;加入权限对象 SE38 在程序中增加以下块 AUTHORITY-CHECK OBJECT Z_BC_APP ID ZAPP FIELD 01. IF sy-subrc EQ 0. ENDIF. 五、…

linux0.11源码分析第二弹——setup.s内容

&#x1f680; 前言 继上篇博客分享了boot文件的内容后&#xff0c;本篇博客进而来到第二个文件&#xff1a; setup.s &#xff0c;对应了《linux源码趣读》的第5~8回。这部分的功能主要就是做了 三件事 &#xff0c;第一件事是做代码搬运和临时变量存放&#xff0c;第二件事是…

Halcon中histo_2dim(Operator)算子原理及应用详解

在Halcon中&#xff0c;histo_2dim算子是一个用于计算双通道灰度值图像的直方图的工具。以下是对该算子的原理及应用的详细解释&#xff1a; 一、原理 histo_2dim算子的函数原型为&#xff1a;histo_2dim(Regions, ImageCol, ImageRow : Histo2Dim : : )。 输入参数&#xff…

(vue)el-table在表头添加筛选功能

(vue)el-table在表头添加筛选功能 筛选前&#xff1a; 选择条件&#xff1a; 筛选后&#xff1a; 返回数据格式: 代码: <el-tableref"filterTable":data"projectData.list"height"540":header-cell-style"{border-bottom: 1px soli…

使用 Marp 将 Markdown 导出为 PPT 后不可编辑的原因说明及解决方案

Marp 是一个流行的 Markdown 演示文稿工具&#xff0c;能够将 Markdown 文件转换为 PPTX 格式。然而&#xff0c;用户在使用 Marp 导出 PPT 时&#xff0c;可能会遇到以下问题&#xff1a; 导出 PPT 不可直接编辑的原因 根据 Marp GitHub 讨论&#xff0c;Marp 导出的 PPTX 文…

UE5安装Fab插件

今天才知道原来Fab也有类似Quixel Bridge的插件&#xff0c;于是立马就安装上了&#xff0c;这里分享一下安装方法 在Epic客户端 - 库 - Fab Library 搜索 Fab 即可安装Fab插件 然后重启引擎&#xff0c;在插件面板勾选即可 然后在窗口这就有了 引擎左下角也会多出一个Fab图标…

Gin- Cookie\Session相关

Cookie&#xff0c;Session是什么&#xff1f; Cookie直译小饼干&#xff0c;是一些数据信息&#xff0c;类似于小型文本文件&#xff0c;存储在浏览器上。Cookie是进行第一次登录之后&#xff0c;由服务器创建后返回给浏览器的。之后&#xff0c;每当浏览器再次向同一服务器发…

使用Python打造高效的PDF文件管理应用(合并以及分割)

在日常工作和学习中&#xff0c;我们经常需要处理大量PDF文件。手动合并、分割PDF不仅耗时&#xff0c;还容易出错。今天&#xff0c;我们将使用Python的wxPython和PyMuPDF库&#xff0c;开发一个强大且易用的PDF文件管理工具。 C:\pythoncode\new\mergeAndsplitPdf.py 所有代…

深度学习中自适应学习率调度器

传统观点认为&#xff0c;太大的学习率不利于优化深度神经网络&#xff0c;而相比固定的学习率而言&#xff0c;变化的学习率更能提供快速的收敛。基于此&#xff0c;本文作者基于理论基础提出了一个计算深度神经网络学习率的新方法。实验结果证明了该方法的有效性。 训练神经…

文献研读|基于像素语义层面图像重建的AI生成图像检测

前言&#xff1a;本篇文章主要对基于重建的AI生成图像检测的四篇相关工作进行介绍&#xff0c;分别为基于像素层面重建的检测方法 DIRE 和 Aeroblade&#xff0c;以及基于语义层面重建的检测方法 SimGIR 和 Zerofake&#xff1b;并对相应方法进行比较。 相关文章&#xff1a;论…

ElasticSearch06-分片节点分配

零、文章目录 ElasticSearch06-分片节点分配 1、单节点多分片多副本 &#xff08;1&#xff09;启动一个空节点 节点的配置如下 cluster.name: mycluster node.name: node-01 node.master: true node.data: true network.host: 127.0.0.1 http.port: 9201 transport.tcp.p…

信息学奥赛一本通 1438:灯泡 | 洛谷 P5931 [清华集训2015] 灯泡

【题目链接】 ybt 1438&#xff1a;灯泡 洛谷 P5931 [清华集训2015] 灯泡 【题目考点】 1. 三分 求函数极值 2. 相似三角形 3. 对钩函数 【解题思路】 首先考虑影子还没有到达对面墙壁的情况 记BM长度为x&#xff0c;影子为AM&#xff0c;长度为L。三角形ABC相似于三角…

揭开 Choerodon UI 拖拽功能的神秘面纱

01 引言 系统的交互方式主要由点击、选择等组成。为了提升 HZERO 系统的用户体验、减少部分操作步骤&#xff0c;组件库集成了卓越的拖拽功能&#xff0c;让用户可以更高效流畅的操作系统。 例如&#xff1a;表格支持多行拖拽排序、跨表数据调整、个性化调整列顺序&#xff1…

【物联网技术与应用】实验4:继电器实验

实验4 继电器实验 【实验介绍】 继电器是一种用于响应施加的输入信号而在两个或多个点或设备之间提供连接的设备。换句话说&#xff0c;继电器提供了控制器和设备之间的隔离&#xff0c;因为设备可以在AC和DC上工作。但是&#xff0c;他们从微控制器接收信号&#xff0c;因此…

fpga系列 HDL:Quartus II 时序约束 静态时序分析 (STA) test.out.sdc的文件结构

test.out.sdc的文件结构 ## Generated SDC file "test.out.sdc"## Copyright (C) 1991-2013 Altera Corporation ## Your use of Altera Corporations design tools, logic functions ## and other software and tools, and its AMPP partner logic ## functions,…

Windows安全中心(病毒和威胁防护)的注册

文章目录 Windows安全中心&#xff08;病毒和威胁防护&#xff09;的注册1. 简介2. WSC注册初探3. WSC注册原理分析4. 关于AMPPL5. 参考 Windows安全中心&#xff08;病毒和威胁防护&#xff09;的注册 本文我们来分析一下Windows安全中心&#xff08;Windows Security Center…