C++ 智能指针完整详解

一、智能指针的核心基础

1. 核心作用

智能指针是C++标准库(STL) 提供的模板类,本质是对原生裸指针(raw pointer)的封装,核心解决2个致命问题:

  • 手动管理动态内存时,忘记调用delete/delete[]导致的内存泄漏
  • 程序异常退出时,delete语句执行不到导致的内存泄漏
  • 规避原生指针的野指针、二次释放等问题。

2. 实现原理

所有智能指针的底层都基于 C++ 经典的 RAII 编程思想

RAII(资源获取即初始化):在构造函数中申请/持有资源(这里是动态内存),在析构函数中释放资源。智能指针是栈上的对象,当栈对象生命周期结束(出作用域、异常退出),析构函数一定会被调用,内存就一定会被释放,无需手动管理。

3. 头文件

所有C++标准智能指针,都定义在 <memory> 头文件中,使用前必须包含:

#include<memory>

二、C++ 所有标准智能指针(按重要性排序)

✅ 1. std::unique_ptr 「独占型智能指针」—— 最常用、优先级最高

核心特性

unique_ptr独占所有权的智能指针:一块动态内存,永远只能被一个 unique_ptr 指向,不允许拷贝、不允许赋值。

  • 它是 C++11 新增的,轻量级(无额外内存开销,和原生指针效率一致);
  • 支持指向单个对象,也原生支持指向数组unique_ptr<int[]>);
  • 支持指定自定义删除器,用于释放特殊资源(比如文件句柄、网络句柄)。
关键规则
  • ❌ 禁止拷贝:unique_ptr<int> p2 = p1; 编译报错;
  • ❌ 禁止赋值:p2 = p1; 编译报错;
  • ✅ 允许移动:通过 std::move() 转移内存的所有权,转移后原指针变为空指针,新指针持有内存,这是唯一合法的“传递”方式。
常用示例
#include<memory>#include<iostream>usingnamespace std;intmain(){// 方式1:创建指向单个对象的unique_ptr(推荐make_unique,C++14支持) unique_ptr<int> p1 =make_unique<int>(10); cout <<*p1 << endl;// 输出:10// 方式2:创建指向数组的unique_ptr(原生支持,无需额外处理) unique_ptr<int[]> p2 =make_unique<int[]>(5);for(int i =0; i <5;++i) p2[i]= i;// 数组正常赋值// 方式3:移动语义转移所有权 unique_ptr<int> p3 =move(p1); cout <<*p3 << endl;// 输出:10if(p1 ==nullptr){ cout <<"p1已为空"<< endl;// 输出:p1已为空}// 手动释放内存(可选,析构时会自动释放,reset等价于置空+释放) p3.reset();return0;}
使用场景

优先使用 unique_ptr,这是C++官方推荐的首选智能指针,满足90%的场景:

  • 所有「独占内存」的场景(一个对象只被一个指针管理);
  • 函数的返回值(返回动态对象,所有权转移给调用方);
  • 类的成员变量(持有类的私有资源,避免拷贝);
  • 容器存储(比如 vector<unique_ptr<int>>,利用移动语义存储)。

✅ 2. std::shared_ptr 「共享型智能指针」—— 共享所有权场景必备

核心特性

shared_ptr共享所有权的智能指针:一块动态内存,可以被多个 shared_ptr 共同指向,所有指针共享同一块内存的所有权。

  • 它是 C++11 新增的,底层通过引用计数(reference count) 实现;
  • 支持指向单个对象,C++17开始原生支持数组shared_ptr<int[]>);
  • 支持自定义删除器,支持拷贝、赋值,使用灵活。
核心原理:引用计数

shared_ptr 内部维护一个引用计数器,记录当前有多少个 shared_ptr 指向同一块内存:

  1. 新的 shared_ptr 指向该内存(拷贝、赋值),计数器 +1
  2. 当某个 shared_ptr生命周期结束(出作用域、reset),计数器 -1
  3. 当计数器变为 0 时(没有任何指针指向这块内存了),自动调用 delete 释放内存。
关键规则
  • ✅ 允许拷贝:shared_ptr<int> p2 = p1; 合法,计数器+1;
  • ✅ 允许赋值:p2 = p1; 合法,p2原内存的计数器-1,p1内存的计数器+1;
  • ✅ 可以通过 use_count() 查看当前引用计数;
  • ✅ 可以通过 unique() 判断是否是唯一持有者(计数器=1)。
常用示例
#include<memory>#include<iostream>usingnamespace std;intmain(){// 方式1:创建shared_ptr(推荐make_shared,C++11支持,效率更高) shared_ptr<int> p1 =make_shared<int>(20); cout <<*p1 << endl;// 输出:20 cout << p1.use_count()<< endl;// 输出:1(只有p1指向内存)// 拷贝:计数器+1 shared_ptr<int> p2 = p1; cout << p2.use_count()<< endl;// 输出:2(p1、p2都指向内存)// 赋值:计数器变化 shared_ptr<int> p3; p3 = p2; cout << p3.use_count()<< endl;// 输出:3// 手动减少引用计数 p1.reset(); cout << p3.use_count()<< endl;// 输出:2(p1释放,计数器-1)return0;// 函数结束,p2、p3生命周期结束,计数器减到0,内存释放}
使用场景

✅ 适用于多个地方需要共享同一个对象的场景:

  • 多个函数、多个对象、多个容器需要访问同一块内存;
  • 容器存储(vector<shared_ptr<int>>),支持自由拷贝和遍历;
  • 类之间的关联关系(比如多个子对象指向同一个父对象)。
⚠️ 致命缺陷:循环引用(内存泄漏)

这是 shared_ptr唯一的坑,也是高频面试题:

循环引用:两个(或多个)shared_ptr 互相指向对方,导致它们的引用计数器永远无法减到0,最终内存泄漏。

典型场景:双向链表的节点、父子对象互相持有对方的 shared_ptr

// 循环引用示例:内存泄漏!structNode{int val; shared_ptr<Node> next;// 节点指向另一个节点};intmain(){ shared_ptr<Node> n1 =make_shared<Node>(); shared_ptr<Node> n2 =make_shared<Node>(); n1->next = n2;// n1持有n2 n2->next = n1;// n2持有n1// 函数结束时,n1和n2的计数器都是2,减到1后不再变化,内存永远不释放!return0;}

这个问题的唯一解决方案,就是下面的 std::weak_ptr


✅ 3. std::weak_ptr 「弱引用型智能指针」—— 仅用于解决shared_ptr的循环引用

核心定位

weak_ptr 是 C++11 新增的,辅助型智能指针不能独立使用必须配合 shared_ptr 一起使用!它不是一个真正的“智能指针”,而是 shared_ptr 的“补充工具”。

核心特性

weak_ptr弱引用:可以指向 shared_ptr 管理的内存,但不会增加引用计数,也不拥有内存的所有权

  • ❌ 没有重载 operator*operator->不能直接解引用访问内存
  • ❌ 不能单独创建,只能通过 shared_ptr 赋值/拷贝得到;
  • ✅ 不会导致循环引用,因为它不增加引用计数;
  • ✅ 可以检测自己指向的内存是否还存在(判活),避免访问野指针。
核心作用

唯一用途:解决 std::shared_ptr 的循环引用问题,没有其他场景需要使用 weak_ptr

核心使用方法
  1. 通过 shared_ptr 构造 weak_ptrweak_ptr<int> wp = sp;(sp是shared_ptr);
  2. 要访问内存时,必须通过 wp.lock() 方法升级为 shared_ptr
    • 如果内存还存在lock() 返回一个有效的 shared_ptr,此时可以正常解引用;
    • 如果内存已释放lock() 返回一个空的 shared_ptr,避免访问野指针;
  3. 可以通过 wp.expired() 判断内存是否已释放(等价于 wp.lock() == nullptr)。
解决循环引用示例(关键修复)
structNode{int val; weak_ptr<Node> next;// 把shared_ptr改为weak_ptr,问题解决!};intmain(){ shared_ptr<Node> n1 =make_shared<Node>(); shared_ptr<Node> n2 =make_shared<Node>(); n1->next = n2;// weak_ptr指向n2,引用计数不增加 n2->next = n1;// weak_ptr指向n1,引用计数不增加// 函数结束时,n1和n2的计数器减到0,内存正常释放!return0;}

❌ 4. std::auto_ptr 「废弃的智能指针」—— 绝对不要使用!

基本背景

auto_ptrC++98 标准中引入的第一个智能指针,也是C++历史上的过渡产物。

核心问题(为什么被废弃)

auto_ptr 表面上是“独占型”智能指针,但它的拷贝/赋值语义存在严重缺陷

  • 它允许拷贝和赋值,但是拷贝/赋值后,原指针会被置为空指针,新指针持有内存;
  • 这种“隐式转移所有权”的行为,会导致代码中出现不可预知的空指针访问,极易引发程序崩溃,是C++中的“坑中之坑”。
废弃&移除标准
  • C++11:标记为废弃,引入 unique_ptr 替代它,unique_ptr 从语法上禁止拷贝,彻底规避了这个问题;
  • C++17:完全移除,标准库中不再提供 std::auto_ptr
结论
✅ 记住:永远不要在任何C++代码中使用 std::auto_ptr,无论是新项目还是老项目,都要替换为 std::unique_ptr

三、补充:智能指针的创建方式(推荐写法)

所有智能指针都有两种创建方式,强烈推荐第一种

方式1:使用 make_xxx 函数(推荐,优先级最高)

  • std::make_unique<T>(args):创建 unique_ptr,C++14 支持;
  • std::make_shared<T>(args):创建 shared_ptr,C++11 支持。

优点

  1. 代码更简洁,无需手动写 new
  2. 内存分配更高效(make_shared 会一次性分配对象内存和引用计数内存);
  3. 避免内存泄漏(如果构造函数抛出异常,make_xxx 会自动释放已申请的内存);
  4. 避免裸指针暴露,更安全。

方式2:通过裸指针构造(不推荐,应急使用)

// 应急写法,不推荐 unique_ptr<int>p1(newint(10)); shared_ptr<int>p2(newint(20));

缺点:如果构造过程中抛出异常,可能导致内存泄漏,且代码可读性差。


四、核心总结(必记,面试高频)

1. 智能指针核心价值

用 RAII 思想自动管理动态内存,彻底解决手动 new/delete 导致的内存泄漏问题。

2. 所有标准智能指针速览

智能指针C++标准核心特性能否独立使用核心用途优先级
std::unique_ptrC++11独占所有权、轻量级、无拷贝✅ 能90%的场景,优先使用⭐⭐⭐⭐⭐
std::shared_ptrC++11共享所有权、引用计数、可拷贝✅ 能多指针共享内存的场景⭐⭐⭐⭐
std::weak_ptrC++11弱引用、不增计数、依赖shared❌ 不能解决shared_ptr的循环引用⭐⭐⭐
std::auto_ptrC++98独占所有权、拷贝有坑✅ 能已废弃,绝对不用

3. 选型优先级(黄金法则)

能用 unique_ptr,就不用 shared_ptr;weak_ptr 只在解决循环引用时用
  1. 优先选择 std::unique_ptr:轻量、高效、无坑,覆盖绝大多数场景;
  2. 只有需要共享内存所有权时,才选择 std::shared_ptr
  3. 只有在 std::shared_ptr 出现循环引用时,才用 std::weak_ptr 修复;
  4. 彻底忘掉 std::auto_ptr

✨ 面试高频考点(补充)

  1. 智能指针的实现原理?—— RAII 思想,构造持有资源,析构释放资源。
  2. unique_ptr 和 shared_ptr 的区别?—— 独占vs共享,有无引用计数,效率差异。
  3. shared_ptr 的坑是什么?怎么解决?—— 循环引用,用 weak_ptr 解决。
  4. weak_ptr 为什么能解决循环引用?—— 弱引用不增加引用计数。
  5. auto_ptr 为什么被废弃?—— 拷贝赋值会导致原指针为空,易崩溃。

Read more

“现在的AI就像1880年的笨重工厂!”微软CSO斯坦福泼冷水:别急着造神

“现在的AI就像1880年的笨重工厂!”微软CSO斯坦福泼冷水:别急着造神

大模型仍未对上商业的齿轮? 编译 | 王启隆 来源 | youtu.be/aWqfH0aSGKI 出品丨AI 科技大本营(ID:rgznai100) 现在的硅谷,空气里都飘着一股“再不上车就晚了”的焦躁感。 最近 OpenClaw 风头正旺,强势登顶 GitHub,终结了 React 神话,许多人更是觉得“AI 自己干活赚钱”的日子就在明天了。 特别是在斯坦福商学院(GSB)这种地方,台下坐着的都是成天琢磨怎么用下一个技术风口搞个独角兽出来的狠人。 微软的首席科学官(CSO)Eric Horvitz 被请到了这个几乎全美最想用 AI 变现的礼堂里。作为从上世纪 80 年代就开始搞 AI 的绝对老炮、也是微软技术底座的“扫地僧”,这位老哥并没有顺着台下的胃口,去吹捧下个月大模型又要颠覆什么行业,而是兜头给大家浇了一盆带点学术味的冷水。 他讲了一个挺有画面感的比喻:大家都在聊

By Ne0inhk
Godot被AI代码“围攻”!维护者崩溃发声:“不知道还能坚持多久”

Godot被AI代码“围攻”!维护者崩溃发声:“不知道还能坚持多久”

整理 | 郑丽媛 出品 | ZEEKLOG(ID:ZEEKLOGnews) 当大模型能在几秒钟内生成一段“看起来像那么回事”的补丁时,开源社区却开始付出另一种代价。 最近,开源游戏引擎 Godot 的核心维护团队公开吐槽:他们正被大量“AI 生成的低质量代码”淹没。那些代码往往结构完整、注释齐全、描述洋洋洒洒,但真正的问题是——提交者可能并不理解自己交上来的内容。 这件事,并不是简单的“有人偷懒用 AI 写代码”。它正在触及开源协作最核心的东西:信任。 一场悄无声息的“AI 洪水” 事情的导火索来自一条 Bluesky 讨论帖。 Godot 主要维护者之一、同时也是 Godot 商业支持公司 W4 Games 联合创始人的 Rémi Verschelde 表示,所谓的“AI slop”

By Ne0inhk
诺奖得主辛顿最新访谈:1 万个 AI 可以瞬间共享同一份“灵魂”,这就是为什么人类注定被超越

诺奖得主辛顿最新访谈:1 万个 AI 可以瞬间共享同一份“灵魂”,这就是为什么人类注定被超越

当宇宙级的“嘴炮”遇到降维打击。 编译 | 王启隆 来源 | youtu.be/l6ZcFa8pybE 出品丨AI 科技大本营(ID:rgznai100) 打开最新一期知名播客 StarTalk 的 YouTube 评论区,最高赞的一条留言是这样写的: “我长这么大,第一次看到尼尔·德葛司·泰森(Neil deGrasse Tyson)在一档节目里几乎全程闭嘴,像个手足无措的小学生一样乖乖听讲。” 作为全美最知名的天体物理学家,泰森平时的画风是充满激情、喋喋不休、用宇宙的宏大来震撼嘉宾。但这一次,坐在他对面的那位满头银发、带着温和英音的英国老人,仅仅用最平淡的语气,就让整个演播室陷入了数次令人窒息的沉默。 这位老人是 Geoffrey Hinton。深度学习三巨头之一,2024 年诺贝尔物理学奖得主,被公认为“AI 教父”。 对经常阅读 Hinton 演讲的我来说,这也是比较新奇的一幕—

By Ne0inhk
48小时“烧光”56万!三人创业团队濒临破产,仅因Gemini API密钥被盗:“AI账单远超我们的银行余额”

48小时“烧光”56万!三人创业团队濒临破产,仅因Gemini API密钥被盗:“AI账单远超我们的银行余额”

整理 | 苏宓 出品 | ZEEKLOG(ID:ZEEKLOGnews) 「仅过了 48 小时,一笔 8.2 万美元的天价费用凭空出现,较这家小型初创公司的正常月费暴涨近 46000%。」 这不是假设的虚幻故事,而是一家墨西哥初创公司正在经历的真实危机。 近日,一位名为 RatonVaquero 的开发者在 Reddit 发帖求助称,由于他的 Gemini API 密钥被盗用,原本每月仅约 180 美元(约 1242 元)的费用,在短短 48 小时内暴涨到 82,314.44 美元(约 56.8 万元)。对于这家只有三名开发者的小型创业团队来说,这笔突如其来的账单,几乎等同于灭顶之灾。 “我现在整个人都处在震惊和恐慌之中。”RatonVaquero

By Ne0inhk