讲师中心 微信公众号
AI工具推荐 视频效率加速

智能指针在STL中应用 shared_ptr使用场景分析

轻杰大大_5246

轻杰大大_5246

发布时间:2025-08-14 11:46:02

|

679人浏览过

|

来源于php中文网

原创

shared_ptr是内存管理的理想选择,因为它通过引用计数机制实现共享所有权,允许多个指针安全地共享同一资源,当最后一个shared_ptr销毁时资源自动释放,避免内存泄漏和悬空指针;在多所有权场景下,如缓存、图形渲染或事件系统,它能自动管理复杂生命周期;为防止循环引用导致内存泄漏,应使用weak_ptr打破引用环,weak_ptr不增加引用计数,仅作为观察者通过lock()安全访问对象;相比unique_ptr(独占所有权,轻量高效,适用于单一所有者场景)和原始指针(无所有权管理,易出错,仅用于性能敏感或非拥有观察),shared_ptr适用于资源被多方共享且生命周期不确定的场景,三者应根据所有权模型合理选用。

智能指针在STL中应用 shared_ptr使用场景分析

shared_ptr
在STL中,本质上是一种智能指针,它以引用计数的方式管理动态分配对象的生命周期,确保当没有任何
shared_ptr
实例指向该对象时,对象能够被安全地自动销毁。它的核心价值在于实现了“共享所有权”的概念,让多个指针可以安全地共同管理同一个资源,而无需手动追踪谁是最终的责任人。

解决方案

使用

std::shared_ptr
来管理资源非常直接。最推荐的方式是使用
std::make_shared
来创建对象,因为它能够一次性分配内存,同时用于对象本身和
shared_ptr
内部的控制块(包含引用计数等信息),这通常比先
new
再构造
shared_ptr
更高效且更安全,可以避免一些异常安全问题。

当你复制一个

shared_ptr
,或者将其赋值给另一个
shared_ptr
时,引用计数会自动增加。当一个
shared_ptr
实例离开其作用域(比如函数返回、局部变量销毁),或者被重新赋值指向另一个对象时,引用计数会自动减少。当引用计数降到零时,
shared_ptr
会自动调用其内部指向对象的析构函数,并释放内存。

#include <iostream>
#include <memory>
#include <vector>

class MyResource {
public:
    MyResource(int id) : id_(id) {
        std::cout << "MyResource " << id_ << " created.\n";
    }
    ~MyResource() {
        std::cout << "MyResource " << id_ << " destroyed.\n";
    }
    void doSomething() {
        std::cout << "MyResource " << id_ << " doing something.\n";
    }
private:
    int id_;
};

void processResource(std::shared_ptr<MyResource> res) {
    std::cout << "Inside processResource, ref count: " << res.use_count() << "\n";
    res->doSomething();
} // res 离开作用域,引用计数减一

int main() {
    // 推荐使用 make_shared
    auto res1 = std::make_shared<MyResource>(1);
    std::cout << "res1 ref count after creation: " << res1.use_count() << "\n";

    // 复制 shared_ptr,引用计数增加
    std::shared_ptr<MyResource> res2 = res1;
    std::cout << "res1 ref count after copy to res2: " << res1.use_count() << "\n";

    processResource(res1); // 传递 shared_ptr,临时增加引用计数

    std::cout << "res1 ref count after processResource: " << res1.use_count() << "\n";

    // 将 shared_ptr 放入容器
    std::vector<std::shared_ptr<MyResource>> resources;
    resources.push_back(res1);
    std::cout << "res1 ref count after push_back: " << res1.use_count() << "\n";

    // 当所有 shared_ptr 都离开作用域,对象才会被销毁
    // 比如 res1, res2, 以及 resources 向量中的元素都销毁后
    // MyResource 1 才会被销毁
    return 0;
}

为什么在多所有权场景下,
shared_ptr
是内存管理的理想选择?

在我看来,

shared_ptr
最闪耀的舞台,就是那些资源需要被多个“所有者”同时使用,且这些所有者对资源的生命周期都有发言权,但又无法确定谁是最后一个使用者的场景。这和那种“一个萝卜一个坑”的独占式所有权(
unique_ptr
的领域)完全不同。

想象一下,你有一个缓存系统,里面存放着一些从数据库加载进来的数据对象。这些数据对象可能会被UI层、业务逻辑层、甚至后台任务同时访问。如果用原始指针,你很难判断什么时候可以安全地释放这些数据:是UI关闭了?还是业务逻辑处理完了?如果某个后台任务还在异步处理它呢?这时,

shared_ptr
的引用计数机制就显得非常优雅了。每个需要访问这个数据的地方都持有一个
shared_ptr
,只要有一个
shared_ptr
还“活着”,数据就不会被销毁。只有当所有持有者都放弃了对它的引用,数据才会自动清理。这极大地简化了复杂的资源生命周期管理,避免了手动释放可能带来的悬空指针或重复释放的问题。

再比如,在图形渲染中,一个纹理资源可能被多个渲染对象共享,每个对象都需要这个纹理,但谁也不是它的唯一拥有者。或者在事件驱动系统中,一个事件对象可能被多个监听器订阅,每个监听器都可能在处理它。这些都是

shared_ptr
大展身手的地方。它提供了一种透明且自动的资源管理方式,让开发者可以更专注于业务逻辑本身,而不是纠结于内存的分配与释放。

使用
shared_ptr
时如何避免循环引用导致的内存泄漏?

这是一个非常经典且容易踩的坑,也是

shared_ptr
设计中一个不得不面对的副作用。当两个或多个对象通过
shared_ptr
互相引用时,它们会形成一个引用环。例如,对象A持有一个指向对象B的
shared_ptr
,同时对象B又持有一个指向对象A的
shared_ptr
。在这种情况下,即使外部已经没有其他
shared_ptr
指向A或B,它们的引用计数也永远不会降到零(因为它们互相引用),从而导致这两个对象及其关联的内存永远不会被释放,造成内存泄漏。

C++ Code Review Master
C++ Code Review Master

组合式C++代码评审方案,融合静态分析、AI推理、多轮迭代评审和C++专项检查,适用于PR审查、增量代码审查、全项目评审和代码质量评分,触发词包括review cpp、cpp代码评审、C++review、代码审查。

下载

解决这个问题的“利器”就是

std::weak_ptr
。
weak_ptr
是一种非拥有型的智能指针。它指向一个由
shared_ptr
管理的对象,但它本身不增加对象的引用计数。这意味着
weak_ptr
不会阻止对象被销毁。你可以把
weak_ptr
想象成一个“观察者”或者“旁观者”,它知道某个对象存在,但它不参与对象的生命周期管理。

当你想访问

weak_ptr
指向的对象时,你需要先调用它的
lock()
方法。
lock()
会尝试返回一个
shared_ptr
。如果对象仍然存在(即引用计数大于零),
lock()
会成功返回一个有效的
shared_ptr
,此时引用计数会临时增加;如果对象已经被销毁,
lock()
会返回一个空的
shared_ptr
。通过检查
lock()
的返回值是否为空,你就可以安全地判断对象是否仍然有效。

在设计数据结构时,特别是涉及到父子关系、观察者模式或图结构时,如果存在循环引用,通常的做法是让强所有权(

shared_ptr
)指向下游或被观察者,而让上游或观察者持有对下游的
weak_ptr
。例如,一个父节点拥有子节点的
shared_ptr
,但子节点如果需要引用父节点,则应该使用
weak_ptr
。这样,当父节点被销毁时,子节点的引用计数会减少,最终子节点也会被销毁,从而打破循环。这种模式既能确保对象被正确管理,又能有效避免内存泄漏。

shared_ptr
与
unique_ptr
、原始指针相比,各自的适用场景是什么?

选择哪种指针,很多时候取决于你对资源“所有权”的理解和设计。这三者各有各的优势和适用范围,没有绝对的优劣之分。

std::unique_ptr
代表的是“独占所有权”。顾名思义,一个资源只能被一个
unique_ptr
拥有。当这个
unique_ptr
被销毁时,它所管理的资源也会被销毁。它的特点是轻量级,几乎没有运行时开销(除了可能的析构函数调用),因为它不需要维护引用计数。它的所有权是可转移的,但不可复制。这使得它非常适合实现RAII(Resource Acquisition Is Initialization)原则,比如管理文件句柄、网络连接、或者只在特定作用域内使用的动态内存。如果你明确知道一个对象只有一个所有者,并且这个所有者的生命周期就是对象的生命周期,那么
unique_ptr
无疑是最佳选择。它比
shared_ptr
更高效,也更明确地表达了设计意图。

std::shared_ptr
,如前所述,代表的是“共享所有权”。它允许多个
shared_ptr
实例共同管理同一个资源。它的开销相对
unique_ptr
要大一些,因为它需要维护一个引用计数,并且引用计数的增减通常涉及到原子操作(为了线程安全)。它适用于资源需要被多个模块或对象共享,且其生命周期由所有引用它的模块共同决定的场景。当最后一个
shared_ptr
被销毁时,资源才会被释放。这种模式在构建复杂的系统,尤其是需要缓存、对象池、或者多线程环境下共享数据时非常有用。

至于原始指针(raw pointer),它不具备任何所有权语义。它仅仅是一个内存地址。使用原始指针时,内存的管理完全由程序员手动负责。这意味着你需要自己去

new
和
delete
,并确保每次
new
都有对应的
delete
,且不会出现重复
delete
或
delete
后访问的情况。这无疑是内存管理中最容易出错的方式。然而,原始指针并非一无是处。在某些低层级的、性能敏感的代码中,或者当你只是想“观察”一个对象而不承担其所有权时(比如函数参数只是为了读取数据而不改变其生命周期),原始指针仍然有其用武之地。但即便如此,现代C++编程实践中,也强烈建议尽可能使用智能指针来替代原始指针,以减少内存相关的错误。当你从智能指针中获取原始指针并传递给其他函数时,一定要清楚谁负责资源的生命周期,避免混淆。

总结来说,如果资源是独占的,用

unique_ptr
;如果资源是共享的,用
shared_ptr
;而原始指针则更多地作为一种底层工具,在明确知道其风险并能有效管理时才使用,或者作为智能指针的“视图”来传递。

热门AI工具

更多
Atoms
Atoms Hot

Atoms是一款AI智能体工具,第一支自动构建真实业务的 AI 团队。

WorkBuddy

一款AI办公效率工具,主要用于腾讯云推出的AI原生桌面智能体工作台,适合需要提升相关任务效率的用户。

DeepSeek

DeepSeek是一款面向对话、写作、编程和推理场景的AI大模型工具。

SkildArt
SkildArt Hot

SkildArt是一款AI文本写作工具,一站式 AI 视觉创作平台。

豆包大模型

豆包大模型是一款由字节跳动推出的企业级大语言模型服务平台。

音述AI
音述AI Hot

一款AI音频处理工具,主要用于音述AI是一个以“用声音述说故事”为核心的 AI 音乐创作与声音分享社区,适合需要提升相关任务效率的用户。

立刻MV
立刻MV Hot

立刻MV是一款AI文本写作工具,AI 音乐视频(MV)创作工具。

超级简历WonderCV

一款AI办公效率工具,主要用于免费求职简历模版下载制作,应届生职场人必备简历制作神器,适合需要提升相关任务效率的用户。

蛙蛙写作

一款AI论文写作工具,主要用于超级AI智能写作助手,适合需要提升相关任务效率的用户。

相关专题

更多
resource是什么文件
resource是什么文件

Resource文件是一种特殊类型的文件,它通常用于存储应用程序或操作系统中的各种资源信息。它们在应用程序开发中起着关键作用,并在跨平台开发和国际化方面提供支持。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

320

2023.12.20

C++ 智能指针与现代内存管理
C++ 智能指针与现代内存管理

深入讲解 C++ 现代内存管理的核心工具——智能指针,涵盖 unique_ptr 独占所有权语义、shared_ptr 引用计数机制与循环引用问题、weak_ptr 弱引用的应用场景、make_unique/make_shared 工厂函数的性能优势、自定义删除器的编写、RAII 资源管理思想的实践,以及从裸指针迁移到智能指针的重构策略,帮助开发者编写安全无泄漏的现代 C++ 代码。

339

2026.04.23

treenode的用法
treenode的用法

​在计算机编程领域,TreeNode是一种常见的数据结构,通常用于构建树形结构。在不同的编程语言中,TreeNode可能有不同的实现方式和用法,通常用于表示树的节点信息。更多关于treenode相关问题详情请看本专题下面的文章。php中文网欢迎大家前来学习。

2401

2023.12.01

C++ 高效算法与数据结构
C++ 高效算法与数据结构

本专题讲解 C++ 中常用算法与数据结构的实现与优化,涵盖排序算法(快速排序、归并排序)、查找算法、图算法、动态规划、贪心算法等,并结合实际案例分析如何选择最优算法来提高程序效率。通过深入理解数据结构(链表、树、堆、哈希表等),帮助开发者提升 在复杂应用中的算法设计与性能优化能力。

336

2025.12.22

深入理解算法:高效算法与数据结构专题
深入理解算法:高效算法与数据结构专题

本专题专注于算法与数据结构的核心概念,适合想深入理解并提升编程能力的开发者。专题内容包括常见数据结构的实现与应用,如数组、链表、栈、队列、哈希表、树、图等;以及高效的排序算法、搜索算法、动态规划等经典算法。通过详细的讲解与复杂度分析,帮助开发者不仅能熟练运用这些基础知识,还能在实际编程中优化性能,提高代码的执行效率。本专题适合准备面试的开发者,也适合希望提高算法思维的编程爱好者。

377

2026.01.06

C++ 数据结构与算法实现教程合集
C++ 数据结构与算法实现教程合集

以 C++ 为实现语言,系统讲解核心数据结构与算法,涵盖链表(单链表/双链表/环检测)、栈与队列(单调栈/优先队列)、二叉树(遍历/BST/AVL/红黑树)、哈希表(开地址法/链地址法)、图(邻接表/BFS/DFS/Dijkstra/拓扑排序)、常见排序算法(快排/归并/堆排/计数排序)的实现与复杂度分析,同时分享 LeetCode 刷题技巧、竞赛编程常用模板(二分/前缀和/滑动窗口/动态规划),帮助开发者夯实算法基础。

432

2026.05.09

线程和进程的区别
线程和进程的区别

线程和进程的区别:线程是进程的一部分,用于实现并发和并行操作,而线程共享进程的资源,通信更方便快捷,切换开销较小。本专题为大家提供线程和进程区别相关的各种文章、以及下载和课程。

3958

2023.08.10

Python 多线程与异步编程实战
Python 多线程与异步编程实战

本专题系统讲解 Python 多线程与异步编程的核心概念与实战技巧,包括 threading 模块基础、线程同步机制、GIL 原理、asyncio 异步任务管理、协程与事件循环、任务调度与异常处理。通过实战示例,帮助学习者掌握 如何构建高性能、多任务并发的 Python 应用。

1109

2025.12.24

PixTV官网入口地址合集
PixTV官网入口地址合集

本专题汇总了 PixTV AI 一站式视频创作平台的官方入口与使用教程。无需下载软件,浏览器直接访问即可使用。平台将剧本、图像、视频、声音与剪辑整合在“无限画布”中,接入 GPT Image 2.5、Seedance 2.5 等头部模型。本专题整理了从新建画布、角色锚定、分镜拆分到视频生成与导出的完整操作指南,助你快速上手 AI 短剧与漫剧创作。

20

2026.10.10

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
LLVM入门手册
LLVM入门手册

共0课时 | 0人学习

Buffalo框架快速入门指南
Buffalo框架快速入门指南

共0课时 | 0人学习

Conan 2 高级依赖模型介绍
Conan 2 高级依赖模型介绍

共0课时 | 0人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn