讲师中心 微信公众号

工具推荐

视频效率加速
Seko
Seko

AI短剧一键生成成片

updream

一键生成高质量推广成片

剧云
剧云

中文短剧剧本制作平台

立刻MV
立刻MV

低成本歌曲 MV、视频生成

shared_ptr引用计数怎样工作 循环引用问题解决方案

夏丽同学_3311

夏丽同学_3311

发布时间:2025-08-18 17:38:01

|

694人浏览过

|

来源于php中文网

原创

shared_ptr通过引用计数机制管理对象生命周期,每个shared_ptr共享一个控制块,其中记录强引用计数,当强引用计数为0时自动释放资源;循环引用问题发生在多个对象相互以shared_ptr持有对方,导致引用计数无法归零,内存无法释放,例如父子节点间双向强引用;解决方法是将一方改为使用weak_ptr,weak_ptr不增加强引用计数,仅作为观察者,通过lock()安全访问对象,从而打破循环,确保内存正确释放。

shared_ptr引用计数怎样工作 循环引用问题解决方案

shared_ptr
的引用计数机制和循环引用问题是 C++ 智能指针使用中的核心知识点,理解它们对避免内存泄漏至关重要。

shared_ptr 引用计数是怎么工作的

shared_ptr
通过引用计数来管理动态对象的生命周期。每个
shared_ptr
实例指向一个资源(堆上分配的对象),同时共享一个控制块(control block),这个控制块中保存了:

  • 指向实际对象的指针
  • 当前有多少个
    shared_ptr
    共享该对象(强引用计数)
  • 当前有多少个
    weak_ptr
    观察该对象(弱引用计数)
  • 可能还包括自定义删除器、分配器等信息

每当一个新的

shared_ptr
被创建并指向同一个对象时,强引用计数加 1;当一个
shared_ptr
被销毁或重新赋值时,强引用计数减 1。当强引用计数变为 0 时,说明没有
shared_ptr
再持有该对象,此时会自动调用删除器释放对象内存。

举个简单例子:

std::shared_ptr<int> p1 = std::make_shared<int>(42);
std::shared_ptr<int> p2 = p1;  // 引用计数变为 2
p1.reset();                    // 引用计数减为 1,对象未释放
// p2 仍然有效,直到它也被销毁

引用计数的操作是线程安全的(原子操作),多个线程同时增加或减少引用不会导致数据竞争。

循环引用问题是怎么产生的

循环引用发生在两个或多个对象通过

shared_ptr
相互持有对方,导致引用计数永远无法归零,从而造成内存泄漏。

典型场景是父子节点结构中,父节点用

shared_ptr
持有子节点,子节点也用
shared_ptr
持有父节点:

C++14
C++14

C++14 对 C++11 的修正与增强版本,适合旧系统维护和较老工具链兼容。

下载
struct Node;
struct Node {
    std::shared_ptr<Node> parent;
    std::shared_ptr<Node> child;
};

auto node1 = std::make_shared<Node>();
auto node2 = std::make_shared<Node>();
node1->child = node2;
node2->parent = node1;  // 形成循环引用

此时:

  • node1
    的引用计数为 2(外部指针 + node2 的 parent)
  • node2
    的引用计数为 2(外部指针 + node1 的 child)

当

node1
和
node2
离开作用域后,外部引用消失,引用计数各减为 1,但由于彼此还持有对方,计数不为 0,对象不会被销毁,造成内存泄漏。

解决循环引用的方案:使用 weak_ptr

最标准的解决方案是:把循环中的一方改为使用

weak_ptr
。

weak_ptr
是一种不增加引用计数的观察者指针,它指向
shared_ptr
管理的对象,但不影响其生命周期。只有在需要访问对象时,才通过
lock()
方法尝试获取一个临时的
shared_ptr
。

修改上面的例子:

struct Node {
    std::shared_ptr<Node> child;
    std::weak_ptr<Node> parent;  // 改为 weak_ptr
};

auto node1 = std::make_shared<Node>();
auto node2 = std::make_shared<Node>();
node1->child = node2;
node2->parent = node1;  // weak_ptr 不增加引用计数

// 访问父节点时:
if (auto parent = node2->parent.lock()) {
    // parent 是临时的 shared_ptr,安全访问
    std::cout << "Has parent\n";
} else {
    std::cout << "Parent is gone\n";
}

这样:

  • node1
    持有
    node2
    (强引用)
  • node2
    只“观察”
    node1
    (弱引用)
  • 当外部指针释放后,
    node2
    的引用计数变为 0,被销毁
  • 接着
    node1
    的引用计数也变为 0,被销毁
  • 内存正确释放,无泄漏

使用建议和注意事项

  • 谁该用 weak_ptr? 通常“从属”或“反向”引用的一方使用
    weak_ptr
    。比如父持有子用
    shared_ptr
    ,子指向父用
    weak_ptr
    。
  • weak_ptr
    不能直接解引用,必须通过
    lock()
    获取
    shared_ptr
    。
  • lock()
    返回一个
    shared_ptr
    ,如果原对象已释放,则返回空
    shared_ptr
    。
  • weak_ptr
    本身也有引用计数(弱引用计数),用于管理控制块的销毁时机。
  • 避免在
    weak_ptr
    上做
    expired()
    判断后再
    lock()
    ,因为可能有竞态条件,直接
    lock()
    更安全。

基本上就这些。关键点是:shared_ptr 靠引用计数自动管理生命周期,但循环引用会打破这个机制,用 weak_ptr 打破循环是最标准做法。

热门AI工具

更多
Loomy
Loomy Hot

一款AI工具,主要用于科大讯飞发布的桌面级 AI 助理,比 OpenClaw 更易用、更安全!,适合需要提升相关任务效率的用户。

WorkBuddy

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

函子Zion

一款支持AI搭建、可视化编辑和多端发布的全栈无代码开发平台。

花生AI
花生AI Hot

一款AI视频创作工具,主要用于B站推出的AI视频创作工具,适合需要提升相关任务效率的用户。

咔片AIPPT

一款在线AI演示文稿制作工具,可根据主题和内容需求辅助生成PPT结构与页面,提高演示材料制作效率。

音述AI
音述AI Hot

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

豆包大模型

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

DeepSeek

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

Flowith
Flowith Hot

Flowith是一款基于画布和智能体工作流的 AI 协作工作空间。

相关专题

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

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

379

2026.04.23

堆和栈的区别
堆和栈的区别

堆和栈的区别:1、内存分配方式不同;2、大小不同;3、数据访问方式不同;4、数据的生命周期。本专题为大家提供堆和栈的区别的相关的文章、下载、课程内容,供大家免费下载体验。

6867

2023.07.18

堆和栈区别
堆和栈区别

堆(Heap)和栈(Stack)是计算机中两种常见的内存分配机制。它们在内存管理的方式、分配方式以及使用场景上有很大的区别。本文将详细介绍堆和栈的特点、区别以及各自的使用场景。php中文网给大家带来了相关的教程以及文章欢迎大家前来学习阅读。

2828

2023.08.10

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

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

4778

2023.08.10

Postman身份认证怎么配置
Postman身份认证怎么配置

本专题从实际API接口调试场景出发,讲解Postman身份认证的完整配置流程,包括常见认证协议介绍、授权参数设置、访问令牌填写、OAuth 2.0授权及认证失败排查,帮助用户正确完成接口授权与访问验证。

60

2026.11.06

Postman请求头Headers怎么设置
Postman请求头Headers怎么设置

围绕Postman HTTP请求头配置展开,讲解Headers选项的具体使用方式,包括Key和Value参数填写、请求头信息添加、常见Header字段设置及请求头修改方法,帮助用户解决接口调试过程中遇到的请求头配置问题。

40

2026.11.06

Postman接口请求发送教程
Postman接口请求发送教程

详细介绍Postman接口请求的创建与发送方法,涵盖GET和POST请求、URL配置、请求参数、Headers请求头、Body请求体设置以及响应结果查看,帮助用户掌握Postman接口请求的完整操作流程。

40

2026.11.06

DBeaver怎么导入数据
DBeaver怎么导入数据

DBeaver数据导入专题,整理Excel、CSV、SQL、TXT等常见文件的数据导入方法,涵盖目标数据库选择、数据表创建、字段映射、数据类型设置及导入参数配置等操作,帮助用户将本地文件中的数据写入数据库,并了解批量导入、重复数据处理和导入失败等常见问题的解决方法。

60

2026.11.05

DBeaver安全防护教程大全
DBeaver安全防护教程大全

DBeaver安全防护专题,围绕数据库连接安全、账号密码保护和访问权限管理展开,整理SSL/TLS加密连接、SSH隧道、数据库凭据保存及证书验证等配置方法,同时介绍敏感信息保护、远程连接风险和高权限账号使用等注意事项,帮助用户提高数据库日常管理的安全性。

40

2026.11.05

热门下载

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

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
OpenClaw
OpenClaw

共0课时 | 0人学习

C++ Core Guidelines
C++ Core Guidelines

共0课时 | 0人学习

C++ Reference
C++ Reference

共0课时 | 0人学习

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

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