展开菜单
首页 精品内容 本月促销 装机必备 Windows PDF教程 macOS软件 IOS软件 Android AI 专题
全部分类

当前位置:

首页 > 编程开发 > C++中std::shared_ptr使用详解

C++中std::shared_ptr使用详解

std::shared_ptr通过引用计数实现共享所有权的自动内存管理,避免内存泄漏和悬空指针;推荐使用std::make_shared创建,注意循环引用等陷阱。

本文目录

    std::shared_ptr通过引用计数实现共享所有权的自动内存管理,避免内存泄漏和悬空指针;推荐使用std::make_shared创建,注意循环引用等陷阱。

    如何在C++中使用std::shared_ptr_C++共享指针shared_ptr使用指南

    std::shared_ptr在C++中提供了一种智能、自动管理动态内存的方式,它允许我们以共享所有权(shared ownership)的模式来管理堆上的对象。简单来说,它就像一个智能的引用计数器,当指向同一对象的最后一个shared_ptr被销毁时,它会自动释放所管理的对象内存,有效避免了内存泄漏和悬空指针的问题。

    解决方案

    使用std::shared_ptr,最直接的便是创建并让它管理一个对象。通常,我们推荐使用std::make_shared来创建shared_ptr,这不仅效率更高(单次内存分配),也能避免一些潜在的内存泄漏问题。

    #include 
    #include  // 包含 shared_ptr
    
    class MyClass {
    public:
        MyClass() { std::cout << "MyClass 构造\n"; }
        ~MyClass() { std::cout << "MyClass 析构\n"; }
        void doSomething() { std::cout << "MyClass 正在工作...\n"; }
    };
    
    int main() {
        // 推荐方式:使用 std::make_shared
        std::shared_ptr ptr1 = std::make_shared();
        ptr1->doSomething();
        std::cout << "ptr1 的引用计数: " << ptr1.use_count() << std::endl; // 通常是1
    
        // 复制 shared_ptr,共享所有权
        std::shared_ptr ptr2 = ptr1;
        std::cout << "ptr1 的引用计数: " << ptr1.use_count() << std::endl; // 此时是2
        std::cout << "ptr2 的引用计数: " << ptr2.use_count() << std::endl; // 此时是2
    
        // 另一个 shared_ptr 也指向同一个对象
        {
            std::shared_ptr ptr3(ptr1); // 或 std::shared_ptr ptr3 = ptr1;
            std::cout << "ptr1 的引用计数: " << ptr1.use_count() << std::endl; // 此时是3
        } // ptr3 在这里离开作用域,引用计数减1
    
        std::cout << "ptr1 的引用计数 (ptr3 离开作用域后): " << ptr1.use_count() << std::endl; // 此时是2
    
        // 可以通过 get() 获取原始指针,但要小心使用,不要手动删除
        MyClass* rawPtr = ptr1.get();
        if (rawPtr) {
            rawPtr->doSomething();
        }
    
        // 重置 shared_ptr,使其不再管理当前对象
        ptr1.reset(); // ptr1 现在为空,引用计数减1
        std::cout << "ptr1 重置后,ptr2 的引用计数: " << ptr2.use_count() << std::endl; // 此时是1
    
        // 当最后一个 shared_ptr (ptr2) 离开作用域时,MyClass 对象将被析构
        return 0;
    }

    这段代码展示了shared_ptr的核心机制:创建、复制、引用计数的变化以及最终的自动释放。通过->和*运算符可以像使用普通指针一样访问其管理的对象。use_count()方法则可以查看当前有多少个shared_ptr实例共享同一个对象。

    std::shared_ptr与普通指针有何不同?为何要选择它?

    说实话,刚接触C++时,指针这东西确实让人又爱又恨。它强大,能直接操作内存,但同时也带来了无数的陷阱:内存泄漏、野指针、重复释放等等。std::shared_ptr的出现,在我看来,就是为了解决这些“人祸”而生。它和普通指针最大的不同,在于它拥有所有权,并且这种所有权是共享的。

    普通指针,说白了,就是个地址,它不关心它指向的内存是谁分配的,谁应该释放。你手动new一个对象,就得手动delete它,否则就是泄漏。如果你不小心delete了两次,程序就崩溃了。更别提多个指针指向同一块内存,你不知道什么时候这块内存被其他指针释放了,你的指针就成了“悬空指针”,访问它就是未定义行为。

    而std::shared_ptr则不然,它内部有一个引用计数器。当你创建一个shared_ptr并让它指向一个对象时,引用计数器就初始化为1。每当你复制这个shared_ptr(比如std::shared_ptr p2 = p1;),引用计数器就加1。当一个shared_ptr离开作用域或被重置时,引用计数器就减1。只有当引用计数器归零时,也就是没有shared_ptr再指向这个对象时,它才会自动调用对象的析构函数并释放内存。这简直是“懒人福音”,把内存管理的重担从程序员肩上卸了下来。

    选择std::shared_ptr的理由很直接:

    1. 自动内存管理:这是核心优势。遵循RAII(Resource Acquisition Is Initialization)原则,资源(内存)的生命周期与对象的生命周期绑定,无需手动释放,大大降低了内存泄漏的风险。
    2. 共享所有权:在多处需要访问并共享同一对象,且对象生命周期不确定时,shared_ptr是理想选择。比如一个资源被多个模块使用,只要有一个模块还在用,资源就不会被释放。
    3. 避免悬空指针:当一个shared_ptr管理的对象被释放后,所有指向它的shared_ptr都会变成空(虽然它们本身并不会自动感知,但use_count为0后,访问行为会更可控,至少不会访问到已释放的内存)。更重要的是,它保证了对象在还有“主人”时不会被提前释放。
    // 示例:普通指针可能带来的问题
    void process_raw_ptr(int* data) {
        if (data) {
            // ... 使用 data
            delete data; // 假设这里要释放
        }
    }
    
    int main_raw() {
        int* my_data = new int(100);
        process_raw_ptr(my_data);
        // 此时 my_data 已经是一个悬空指针,再次访问或 delete 会出问题
        // std::cout << *my_data << std::endl; // 未定义行为
        // delete my_data; // 双重释放
    
        // 使用 shared_ptr 则不会有这些烦恼
        std::shared_ptr shared_data = std::make_shared(100);
        std::shared_ptr shared_data_copy = shared_data;
        // 无论哪个 shared_ptr 离开作用域,只要还有其他 shared_ptr 引用,内存就不会被释放
        // 只有当 shared_data 和 shared_data_copy 都失效后,int(100) 才会析构
        return 0;
    }

    这种由shared_ptr提供的“智能”和“安全”,在现代C++编程中是极其宝贵的。

    std::shared_ptr的常见陷阱与最佳实践是什么?

    虽然shared_ptr极大地简化了内存管理,但它也不是万能的,甚至可以说,如果用得不好,它也会引入一些新的复杂性。

    常见陷阱:

    1. 循环引用 (Circular References):这是shared_ptr最臭名昭著的陷阱。当两个或多个对象相互持有对方的shared_ptr时,它们会形成一个引用环。即使外部没有其他shared_ptr指向它们,它们的引用计数也永远不会降到零,导致这组对象永远不会被释放,造成内存泄漏。

      class B; // 前向声明
      class A {
      public:
          std::shared_ptr b_ptr;
          ~A() { std::cout << "A 析构\n"; }
      };
      
      class B {
      public:
          std::shared_ptr a_ptr;
          ~B() { std::cout << "B 析构\n"; }
      };
      
      void test_circular_ref() {
          std::shared_ptr a = std::make_shared();
          std::shared_ptr b = std::make_shared();
          a->b_ptr = b; // b 的引用计数变为 2
          b->a_ptr = a; // a 的引用计数变为 2
          // 当 a 和 b 离开作用域时,它们的引用计数都还是 1,导致 A 和 B 都不会被析构
      } // A 和 B 都没有析构,内存泄漏!

      解决循环引用通常需要引入std::weak_ptr。weak_ptr是一种不增加引用计数的智能指针,它“观察”shared_ptr管理的对象,但不拥有它。

    2. 从裸指针多次构造shared_ptr:如果你有一个裸指针,然后用它创建了多个独立的shared_ptr,而不是通过复制一个已存在的shared_ptr,那么每个shared_ptr都会有自己的引用计数器,并认为自己是该对象的唯一所有者。当它们各自的引用计数归零时,就会尝试多次释放同一块内存,导致双重释放错误。

      int* raw_ptr = new int(42);
      std::shared_ptr p1(raw_ptr); // p1 认为自己是所有者
      // std::shared_ptr p2(raw_ptr); // 错误!p2 也会认为自己是所有者,导致双重释放
      // 正确的做法是:
      // std::shared_ptr p2 = p1;
    3. 从this指针创建shared_ptr:在类的成员函数中,如果你需要返回当前对象的shared_ptr,直接使用std::shared_ptr(this)也是错误的,因为它同样会创建一个独立的shared_ptr,导致与外部已有的shared_ptr冲突。正确的方法是让类继承std::enable_shared_from_this,并通过shared_from_this()方法获取。

    4. 管理非堆内存或数组:shared_ptr默认调用delete来释放内存。如果它管理的是一个数组(new T[N]),则需要提供一个自定义的deleter来调用delete[]。对于非堆内存(如栈上的对象),shared_ptr也会尝试delete,导致未定义行为。

    最佳实践:

    1. 优先使用std::make_shared:这是最重要的一条。std::make_shared在一个内存块中同时分配对象和其控制块(包含引用计数等信息),减少了一次内存分配,提高了效率,并且避免了在构造对象失败时,控制块仍然被分配的潜在内存泄漏。

      // 推荐
      std::shared_ptr p = std::make_shared();
      // 不推荐,虽然功能上没问题,但效率较低,且可能出现异常安全问题
      // std::shared_ptr p(new MyClass());
    2. 使用std::weak_ptr解决循环引用:当两个对象需要相互引用,但又不希望形成强所有权循环时,让其中一方持有weak_ptr。

      class B_fixed;
      class A_fixed {
      public:
          std::shared_ptr b_ptr;
          ~A_fixed() { std::cout << "A_fixed 析构\n"; }
      };
      
      class B_fixed {
      public:
          std::weak_ptr a_ptr; // 使用 weak_ptr
          ~B_fixed() { std::cout << "B_fixed 析构\n"; }
      };
      
      void test_no_circular_ref() {
          std::shared_ptr a = std::make_shared();
          std::shared_ptr b = std::make_shared();
          a->b_ptr = b;
          b->a_ptr = a; // 这里不会增加 a 的引用计数
      } // a 和 b 都会正常析构
    3. 理解所有权语义:shared_ptr意味着共享所有权。如果一个对象应该只有一个所有者,或者其生命周期由其创建者严格控制,那么std::unique_ptr可能是更好的选择。

    4. 自定义deleter:如果shared_ptr需要管理非标准方式分配的内存(如malloc、new[])或者需要执行特殊的清理操作,可以提供一个自定义的deleter。

      // 管理数组
      std::shared_ptr arr_ptr(new int[10], std::default_delete());
      // 或使用 lambda 作为 deleter
      std::shared_ptr file_ptr(fopen("test.txt", "w"), [](FILE* f) {
          if (f) { fclose(f); std::cout << "文件关闭\n"; }
      });
    5. 避免在shared_ptr管理的对象内部持有其自身的shared_ptr:这通常会导致循环引用或不必要的复杂性。如果需要,请使用std::enable_shared_from_this。

    std::shared_ptr在实际项目中的应用场景与性能考量

    std::shared_ptr在实际项目中有着非常广泛的应用,尤其是在需要对象生命周期管理复杂、多模块共享资源、或者难以明确单一所有者的场景。

    应用场景:

    1. 工厂模式 (Factory Pattern):当工厂函数创建对象并返回给调用者时,通常会返回一个shared_ptr。这样,工厂本身不需要关心对象的生命周期,调用者可以安全地使用,并在不再需要时自动释放。
      // 假设有一个接口 Product
      // std::shared_ptr createProduct(ProductType type);
    2. 缓存管理 (Cache Management):在缓存系统中,数据项可能会被多个消费者同时使用。shared_ptr可以确保只要有任何一个消费者还在使用某个数据项,该数据项就不会被从缓存中移除或销毁。当所有消费者都释放了对它的shared_ptr时,缓存系统可以安全地将其清除。
    3. 多线程环境下的资源共享:在多线程应用中,多个线程可能需要访问同一个数据结构或资源。shared_ptr可以管理这些资源的生命周期,确保资源在所有使用它的线程都结束后才被释放。当然,shared_ptr本身是线程安全的(引用计数的增减是原子操作),但它所管理的对象的数据访问仍需要额外的同步机制(如互斥锁)。
    4. 图形渲染与游戏引擎:纹理、模型、着色器等资源常常被多个游戏对象或渲染通道共享。shared_ptr在这里可以有效地管理这些资源的生命周期,避免重复加载和提前释放。
    5. 事件系统或观察者模式:当一个事件源需要通知多个观察者时,观察者通常会以shared_ptr的形式注册到事件源。这样,只要有观察者存在,事件源就可以安全地向它们发送通知。

    性能考量:

    shared_ptr的便利性并非没有代价,它相比于裸指针和unique_ptr,会引入一些性能开销:

    1. 内存开销:shared_ptr不仅仅是一个指针。它需要一个额外的“控制块”来存储引用计数(强引用和弱引用)、自定义deleter等信息。这个控制块通常与对象本身分开分配(除非使用make_shared),因此每个shared_ptr实例会占用更多的内存。
    2. 运行时开销:
      • 引用计数操作:每次shared_ptr的复制、赋值、销毁都会涉及到引用计数的原子增减操作。原子操作虽然比非原子操作慢,但在多线程环境下是必要的。
      • 构造/析构:shared_ptr的构造和析构比裸指针或unique_ptr更复杂,因为它需要管理控制块和引用计数。
      • make_shared的优势:如前所述,std::make_shared能够将对象和控制块分配在同一块内存上,这不仅减少了内存碎片,也提升了缓存局部性,从而在一定程度上缓解了内存开销和部分运行时开销。

    何时选择shared_ptr,何时选择unique_ptr?

    这是一个很实际的问题。我的经验是,如果一个对象只有一个明确的、排他的所有者,并且所有权可以转移,那么std::unique_ptr是首选。它零运行时开销,内存开销也最小(只比裸指针多一个指针大小)。它强调“独占所有权”,语义更清晰。

    只有当一个对象确实需要被多个部分共享,并且其生命周期难以由单一所有者决定时,才考虑使用std::shared_ptr。比如上面提到的缓存、多线程共享资源等场景。

    总结来说,shared_ptr是一个强大的工具,它在C++中提供了自动的内存管理和共享所有权语义。理解其工作原理、优缺点以及潜在陷阱,并结合实际需求选择合适的智能指针,是编写高效、健壮现代C++代码的关键。

    本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系bd@zhengruan.com
    作者最新文章
    编程开发
    相关文章 更多
    精品专题 更多
    装机必备

    正软商城装机必备专区,精选办公、浏览器、安全防护、影音播放、压缩解压、设计创作和系统工具等电脑常用正版软件,帮助用户快速完成新电脑软件配置。

    Windows

    正软商城Windows软件专区,汇集适用于Windows电脑的办公、设计、安全防护、影音播放、开发工具和系统优化软件,提供软件介绍、系统要求、正版授权及购买下载服务。

    PDF教程

    正软商城PDF教程频道提供PDF编辑、转换、合并、拆分、压缩及格式处理方法,同时介绍常用PDF软件和工具的使用技巧。

    macOS软件

    正软商城macOS软件专区,精选适用于Mac电脑的办公、设计、影音、效率、开发和系统工具,提供软件功能介绍、macOS兼容版本、正版授权及购买下载服务。

    IOS软件

    正软商城iOS软件专区,精选适用于iPhone和iPad的办公、学习、影音、设计、效率及AI应用,提供功能介绍、适用设备、系统要求和正版获取方式等信息。

    AI

    正软商城AI软件专区,汇集AI写作、AI绘画、AI视频、AI办公、AI编程、AI翻译、智能客服和数据分析等人工智能工具,提供功能介绍、适用平台、收费方式及正版购买信息。

    Mac软件 更多
    Postman macOS版
    Postman macOS版

    Postman是一套面向API全生命周期的开发与测试平台,可在Mac上发送和调试多种协议请求,管理集合、环境及认证信息,并完成脚本测试、模拟服务、监控、文档发布和团队协作。它适合开发、测试及运维人员使用,提供个人免费方案。

    Docker Desktop macOS版
    Docker Desktop macOS版

    Docker Desktop 是 Docker 官方为 Mac 提供的容器开发环境,集成 Docker Engine、命令行工具、Compose、Build、Kubernetes 与图形化管理界面。

    Android Studio macOS版
    Android Studio macOS版

    Android Studio 是 Google 面向安卓应用开发提供的官方集成开发环境,基于 IntelliJ IDEA 构建。它整合智能代码编辑、Gradle 构建、界面预览、设备模拟、性能分析、调试测试及 APK 分析等工具。

    Xcode macOS版
    Xcode macOS版

    Xcode 是苹果面向 Mac 提供的官方集成开发环境,将代码编辑、项目构建、SwiftUI 实时预览、设备模拟、自动化测试、断点调试、性能分析和应用分发整合在同一套工作流中,适合开发与维护 iPhone、iPad、Mac、Apple Wa

    JetBrains WebStorm macOS版
    JetBrains WebStorm macOS版

    JetBrains WebStorm 是面向 JavaScript、TypeScript 和现代 Web 技术的专业集成开发环境,可智能理解项目结构,提供代码补全、重构、错误检查、运行调试、测试及 Git 工具。

    PhpStorm macOS版
    PhpStorm macOS版

    PhpStorm 是 JetBrains 推出的专业 PHP 集成开发环境,可在 Mac 上完成代码编写、智能检查、重构、调试、测试和数据库管理。它支持主流 PHP 框架、Composer、Git、Docker 与远程解释器。

    JetBrains PyCharm macOS版
    JetBrains PyCharm macOS版

    JetBrains PyCharm 是面向 Python 开发者的集成开发环境,提供智能补全、代码检查、重构、调试、测试、版本控制及虚拟环境管理功能。Mac 版兼容 Intel 与 Apple 芯片,核心功能可免费使用。

    JetBrains IntelliJ IDEA macOS版
    JetBrains IntelliJ IDEA macOS版

    IntelliJ IDEA 是 JetBrains 推出的专业集成开发环境,重点服务于 Java、Kotlin 与 JVM 项目开发。软件集成代码补全、静态检查、重构、调试、测试、版本控制及构建工具,并可借助插件扩展语言和框架支持。

    Visual Studio Code macOS版
    Visual Studio Code macOS版

    Visual Studio Code 是微软提供的免费代码编辑器,支持 macOS,并为 Intel 与 Apple 芯片 Mac 提供适配版本。软件集代码编辑、调试、Git 源代码管理、集成终端和扩展市场于一体。

    REAPER macOS版
    REAPER macOS版

    REAPER是Cockos开发的数字音频工作站,提供多轨音频与MIDI录制、剪辑、处理、混音和母带制作工具。Mac版兼容Intel与Apple芯片,支持AU、VST、VST3、CLAP等插件格式,并提供高度可定制的工作流程。

    Adobe Audition macOS版
    Adobe Audition macOS版

    Adobe Audition 是面向播客、影视后期、音乐制作及内容创作者的专业音频工作站,提供波形精修、多轨录制与混音、频谱编辑、降噪修复、响度匹配和效果处理工具,并可与 Adobe Premiere 协同完成视频声音制作。

    Pro Tools macOS版
    Pro Tools macOS版

    Pro Tools 是 Avid 推出的专业数字音频工作站,覆盖多轨录音、无损剪辑、MIDI 编曲、插件处理、自动化混音及声音与画面同步等工作。它拥有 Intro、Artist、Studio 和 Ultimate 等方案。

    WINDOWS 更多
    Postman Windows版
    Postman Windows版

    Postman 是面向 API 全生命周期的开发与协作平台。Windows 桌面版可用于构建和发送 HTTP、GraphQL、gRPC、WebSocket、MQTT 等请求,并提供环境变量、集合、脚本测试、模拟服务、监控、文档及团队工作区等

    Docker Desktop Windows版
    Docker Desktop Windows版

    Docker Desktop 为 Windows 开发者提供集成式容器环境,内置 Docker Engine、命令行工具、Compose、Build 与 Kubernetes 等组件,可在本机完成镜像构建、容器运行、日志查看和资源管理。

    Android Studio Windows版
    Android Studio Windows版

    Android Studio 是 Google 面向 Android 应用开发提供的官方集成开发环境,以 IntelliJ IDEA 为基础,集成智能代码编辑、Gradle 构建、设备模拟、界面预览、调试测试及性能分析工具,

    JetBrains WebStorm Windows版
    JetBrains WebStorm Windows版

    WebStorm 是 JetBrains 面向 JavaScript、TypeScript 和现代 Web 技术打造的集成开发环境。它提供项目级代码分析、智能补全、安全重构、导航搜索,并集成调试、测试、终端、Git 与常用前端工具,

    JetBrains PhpStorm Windows版
    JetBrains PhpStorm Windows版

    PhpStorm 是 JetBrains 面向 PHP 与现代 Web 开发打造的专业集成开发环境,提供代码补全、静态检查、重构、调试、测试、数据库、版本控制及前端语言支持。Windows 用户可直接下载安装程序,

    JetBrains PyCharm Windows版
    JetBrains PyCharm Windows版

    PyCharm 是 JetBrains 推出的 Python 集成开发环境,提供智能代码补全、错误检查、重构、调试、测试、Git、终端及 Jupyter Notebook 支持。统一版安装后可体验 30 天 Pro 功能,

    JetBrains IntelliJ IDEA Windows版
    JetBrains IntelliJ IDEA Windows版

    JetBrains IntelliJ IDEA 是以 Java、Kotlin 和 JVM 项目为核心的专业集成开发环境,提供智能补全、代码检查、安全重构、调试、测试、版本控制及构建工具集成。统一发行版的核心功能可免费用于商业和非商业项目,

    Visual Studio Code Windows版
    Visual Studio Code Windows版

    Visual Studio Code 是微软推出的免费代码编辑器,Windows 版集成智能补全、代码导航、调试、终端和版本控制,并可通过扩展添加语言、格式化及远程开发能力。它启动较快、配置灵活,

    REAPER Windows版
    REAPER Windows版

    REAPER 是 Cockos 推出的数字音频工作站,适用于录音、MIDI 编曲、音频编辑、混音、母带制作及声音设计。软件安装包小巧,支持多轨录制、灵活路由、自动化、第三方插件和脚本扩展,并提供完整功能的 60 天免费评估期。

    Adobe Audition Windows版
    Adobe Audition Windows版

    Adobe Audition 是面向播客、影视后期、广播与音乐制作的专业音频工作站,提供波形编辑、多轨混音、频谱分析、录音、降噪修复和效果处理等功能。Windows 版通过 Adobe Creative Cloud 安装和管理,

    Pro Tools Windows版
    Pro Tools Windows版

    Pro Tools 是 Avid 推出的专业数字音频工作站,提供多轨录音、音频剪辑、MIDI编曲、插件处理、自动化混音及影视声音制作能力。它适合音乐人、录音师、制作人和音频后期团队使用,并提供免费入门版、订阅方案及永久授权选择。

    Cubase Windows版
    Cubase Windows版

    Cubase 是 Steinberg 推出的数字音频工作站,提供 MIDI 编曲、多轨录音、音频编辑、虚拟乐器、效果处理、自动化、混音和母带输出工具。其 Pro、Artist 与 Elements 版本面向不同预算和制作规模,

    网站备案号:苏ICP备2026018738号-1 联系邮箱:bd@zhengruan.com

    Copyright ©2018-2026