news 2026/4/16 17:57:50

《你真的了解C++吗》No.016:智能指针的幻觉——unique_ptr 与 shared_ptr 的设计哲学

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
《你真的了解C++吗》No.016:智能指针的幻觉——unique_ptr 与 shared_ptr 的设计哲学

《你真的了解C++吗》No.016:智能指针的幻觉——unique_ptr 与 shared_ptr 的设计哲学

导言:为什么new是危险的?

在传统的 C++ 教程中,我们学习了用new分配内存,用delete释放内存。然而,在逻辑复杂的工程中,由于异常跳转、提前返回或逻辑疏忽,delete往往会被漏掉,导致内存泄漏;或者被多次执行,导致双重释放

智能指针(Smart Pointers)的本质并不是指针,它们是封装了原始指针的“管家”对象。它们利用了 C++ 的 RAII 机制:当管家对象在栈上被销毁时,它会自动在析构函数里帮我们清理堆上的内存。


一、unique_ptr:极致的独占与零开销

unique_ptr遵循的是“独占所有权”模型。它是最符合 C++ “零开销抽象”原则的工具。

  • 设计哲学:一个资源在同一时刻只能有一个主人。
  • 禁止拷贝:你不能把一个unique_ptr赋值给另一个,因为这会导致两个主人争夺同一个资源。
  • 所有权转移:你必须使用std::move()显式地将所有权“转让”出去。
  • 性能:在编译器优化后,unique_ptr的性能与原始指针完全一致。它不占用额外的内存,也没有运行时的计时开销。

二、shared_ptr:复杂的共享与代价

当你确实需要多个对象共同拥有同一块内存时(例如图论中的节点),shared_ptr就上场了。它通过**引用计数(Reference Counting)**来工作。

1. 内存结构的细节:为什么是两个指针?

一个shared_ptr在栈上占用的空间通常是2 个指针的大小(在 64 位系统上为 16 字节),它内部包含:

  • 原始指针(Stored Pointer):直接指向堆上的对象。
  • 控制块指针(Control Block Pointer):指向一个独立的、位于堆上的“控制块”。
2. 控制块里藏着什么?

控制块(Control Block)是shared_ptr共享机制的核心,它由所有指向同一个对象的shared_ptr共同维护,内部包含:

  • 强引用计数(Strong Ref Count):记录当前有多少个shared_ptr指向该对象。当这个计数归零,对象被销毁
  • 弱引用计数(Weak Ref Count):记录当前有多少个weak_ptr指向该对象。
  • 自定义删除器/分配器:如果你指定了如何销毁对象。
3. 代价分析
  • 内存开销:每个shared_ptr实例在栈上比普通指针大一倍。此外,控制块在堆上需要额外申请空间(通常约 16-32 字节)。
  • 性能损耗:引用计数的修改必须是原子的(Atomic)。这意味着即使在单线程逻辑中,每当你拷贝或销毁一个shared_ptr,CPU 都要执行昂贵的原子操作来保证多线程环境下的数据一致性。

三、weak_ptr:打破“死亡环抱”

shared_ptr有一个致命的弱点:循环引用。如果 A 指向 B,B 也指向 A,它们的计数永远不会归零,内存将永久泄漏。

weak_ptr是为了观察shared_ptr而存在的“旁观者”:

  • 它不会增加引用计数。
  • 它不拥有资源。
  • 它能感知资源是否已经被销毁(通过lock()转换为shared_ptr来安全访问)。

四、 避坑指南:为什么make_shared更受欢迎?

永远优先使用std::make_uniquestd::make_shared,而不是直接new出来丢给指针:

  1. 安全性:防止在构造函数参数传递过程中发生异常导致内存泄漏。
  2. 效率(针对 shared_ptr):传统的shared_ptr<T>(new T())需要两次堆内存申请(一次给对象,一次给控制块)。而std::make_shared会一次性申请一块足够大且连续的内存,同时容纳对象和控制块。这减少了内存碎片,且对 CPU 缓存极其友好。

五、 总结:不要为了“安全”而滥用

很多初学者因为害怕内存泄漏,将项目中所有的指针都改成了shared_ptr。这是一种危险的倾向:

  • **默认使用unique_ptr**:它清晰地表达了所有权,且性能最高。
  • **只有在必须共享时才使用shared_ptr**
  • 原始指针仍有用途:如果只是为了“观察”一下对象,而不涉及所有权(即你保证对象的生命周期比这个指针长),使用原始指针(Raw Pointer)往往比weak_ptr更高效、更直接。

下一篇预告:内存管理之后,我们要探讨 C++ 中另一个“既迷人又危险”的特性。它让我们可以写出“生产代码的代码”,让编译器为我们干活。

➡️《你真的了解C++吗》No.017:模板元编程的黑魔法 (The Magic of Template Metaprogramming): SFINAE 与 Concept。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/16 9:20:05

Project CodeNet:解锁大规模代码智能分析的终极指南

Project CodeNet&#xff1a;解锁大规模代码智能分析的终极指南 【免费下载链接】Project_CodeNet This repository is to support contributions for tools for the Project CodeNet dataset hosted in DAX 项目地址: https://gitcode.com/gh_mirrors/pr/Project_CodeNet …

作者头像 李华
网站建设 2026/4/15 21:45:18

图解AI核心技术:大模型、RAG、智能体、MCP

简介本文整理了来自Daily Dose of Data Science最热门或最新的文章&#xff0c;其中极具特色的动图以生动形象的方式&#xff0c;帮助我们更好的理解AI中的一些核心技术&#xff0c;希望能够帮助大家更好的理解和使用AI。大模型Transformer vs. Mixture of Experts混合专家 (Mo…

作者头像 李华
网站建设 2026/4/16 10:55:52

OpenBLAS开源贡献终极指南:3步快速上手高性能计算项目开发

OpenBLAS开源贡献终极指南&#xff1a;3步快速上手高性能计算项目开发 【免费下载链接】OpenBLAS OpenBLAS is an optimized BLAS library based on GotoBLAS2 1.13 BSD version. 项目地址: https://gitcode.com/gh_mirrors/op/OpenBLAS 想要参与开源项目但不知从何入手…

作者头像 李华
网站建设 2026/4/16 9:20:35

PyTorch模型量化压缩:Miniconda环境中实践

PyTorch模型量化压缩&#xff1a;Miniconda环境中实践 在边缘计算和移动AI应用日益普及的今天&#xff0c;一个训练完的深度学习模型动辄数百MB甚至上GB&#xff0c;直接部署到树莓派、手机或嵌入式设备上几乎不可行。更别提推理速度慢、功耗高、内存占用大等一系列问题。如何让…

作者头像 李华
网站建设 2026/4/15 22:36:02

HTML audio标签播放训练完成提醒音效

HTML audio标签播放训练完成提醒音效 在人工智能开发中&#xff0c;模型训练常常需要几分钟到数小时不等。这段时间里&#xff0c;开发者要么盯着终端输出等待进度条走完&#xff0c;要么切换去处理其他任务却担心错过关键节点——尤其是当多个实验并行运行时&#xff0c;很容…

作者头像 李华
网站建设 2026/4/16 10:55:14

Simditor多语言编辑器:构建全球化内容创作平台的完整技术解析

Simditor多语言编辑器&#xff1a;构建全球化内容创作平台的完整技术解析 【免费下载链接】simditor An Easy and Fast WYSIWYG Editor 项目地址: https://gitcode.com/gh_mirrors/si/simditor 在当今数字化时代&#xff0c;内容创作已经跨越国界&#xff0c;面向全球用…

作者头像 李华