C++ 智能指针(附 3 种做法),手把手教你含完整示例与避坑

编程狮(w3cschool.cn) 2026-09-29 12:09:22 浏览数 (23)
反馈

C++ 智能指针三种做法怎么选?

你用 new 分配了内存,却总忘记 delete,或者害怕哪里多删了一次导致程序崩溃?C++ 智能指针,就是替你自动管理内存的“管家”,对象离开作用域时自动释放,基本告别内存泄漏。本文讲清 unique_ptr、shared_ptr、weak_ptr 三种做法、各自适用场景,以及两个最容易踩的坑。看完你能安全地管理动态对象。今天这篇文章,编程狮就把这块讲透。三种指针从独占到共享再到破环,配上完整示例和两个最容易踩的坑,写 C++ 动态对象时心里就有底了。

一、先看结论

你的需求 该用的指针 关键点
独占所有权 std::unique_ptr 不能拷贝,只能移动
共享所有权 std::shared_ptr 引用计数归零才释放
打破循环引用 std::weak_ptr 不增加计数,观察 shared
裸 new/delete 尽量不用 易漏删、易重复删

C++ 智能指针:这块内存归谁所有?

二、为什么需要智能指针

C++ 智能指针 解决的是“谁负责 delete”的问题。传统 new 出来的对象,必须有人记得 delete,函数提前 return、抛异常、或多人共用同一块内存时,很容易漏删(内存泄漏)或重复删(野指针崩溃)。智能指针把“释放”绑定到对象的生命周期上:它本质是个栈上的小对象,析构时自动帮你 delete,你不必再手动管。

一个比喻:裸指针像借来的书你自己记着还,智能指针像图书馆的自动归还机,书到期(离开作用域)自动回收,你不用操心。建议先过一遍 C++ 教程 的类和动态内存章节。

三、做法一:unique_ptr 独占所有权

#include <memory>
#include <iostream>

int main() {
    // make_unique 创建,推荐写法(异常安全)
    std::unique_ptr<int> p = std::make_unique<int>(42);
    std::cout << *p << "\n";   // 解引用拿到 42

    // 所有权只能“移动”,不能拷贝
    std::unique_ptr<int> q = std::move(p);  // 此后 p 为空
    // 函数结束,q 析构,自动 delete 这块内存
}

上面这段做了什么:用 make_unique 创建一个独占的整数对象,p 是唯一主人。把 p 移动给 q 后,p 不再拥有它,最终 q 离开作用域时自动释放内存,不会泄漏。因为“独占”,所以禁止拷贝,只能 move。

四、做法二与三:shared_ptr 与 weak_ptr

#include <memory>

// 做法二:shared_ptr 共享,引用计数管理生命周期
auto a = std::make_shared<int>(10);
auto b = a;            // 拷贝,引用计数 +1;a、b 都失效时才释放

// 做法三:weak_ptr 打破循环引用
struct Node {
    std::shared_ptr<Node> next;   // 若双向互指 shared,计数永不归零
    std::weak_ptr<Node>   prev;   // 用 weak 观察,不增加计数
};

上面这段做了什么:做法二里 a 和 b 共同拥有那块内存,底层引用计数变成 2,只有两者都销毁计数才归零、内存才释放,适合“多个地方都要用同一个对象”的场景。做法三针对一个经典陷阱:如果两个对象互相用 shared_ptr 指向对方,计数永远不为 0,C 与 C++ 怎么选;把其中一个改成 weak_ptr(只观察、不计数)就能打破环。

五、两个最容易踩的坑

现象 原因 修复
内存泄漏,计数不归零 两个对象用 shared_ptr 互指,成环 环的一侧改用 weak_ptr
重复释放 / 悬空 把同一裸指针包进两个独立智能指针 始终用 make_shared/make_unique 创建

第一个坑就是上面说的循环引用,weak_ptr 是标准解法。第二个坑更隐蔽:如果你写 shared_ptr<int> x(new int(1)); shared_ptr<int> y(new int(1)); 这俩独立没问题;但若是 int* raw = new int(1); shared_ptr<int> x(raw); shared_ptr<int> y(raw); ——两个智能指针各自以为自己独占,析构时会 delete 同一块内存两次,直接崩溃。永远用 make_* 创建,别拿同一个裸指针去构造多个智能指针。

C++ 智能指针的本质是“用对象管资源”,把 new 出来的裸指针交给一个栈上对象,等它离开作用域时自动 delete,从而把“手动释放”变成“确定性析构”。unique_ptr 表示独占所有权:同一时刻只有一个 unique_ptr 指向这块内存,不能拷贝只能移动(std::move),开销几乎为零,是默认首选。shared_ptr 表示共享所有权:内部维护一个引用计数,每多一个拷贝计数加一,最后一个销毁时才真正释放;它灵活但有代价——计数本身要原子操作,且容易因循环引用造成内存泄漏。weak_ptr 就是为破环而生的:它指向 shared_ptr 管理的内存但不增加计数,用前先 lock() 提升成 shared_ptr,若对象还活着才用,父子、缓存这类“观察而非拥有”的关系用它最合适。

实战里几条铁律。第一,能用 unique_ptr 就别用 shared_ptr,前者更轻、语义更清晰。第二,创建时用 std::make_unique 和 std::make_shared,而不是手写 new 再塞进智能指针——make_* 更安全(避免异常下泄漏)也更高效(make_shared 能把对象和计数分配到同一块内存)。第三,需要自定义释放动作(比如管理文件句柄、数据库连接)就传删除器;管理数组用 unique_ptr<T[]> 或 shared_ptr<T[]> 的数组特化,别用普通版本导致只 delete 不 delete[]。第四,auto_ptr 早已废弃,见到旧代码里的它直接换成 unique_ptr。

对比裸指针,智能指针把“我什么时候该 delete”这个最容易出错的决策交给了编译器作用域,几乎可以做到零手动 delete。代价是别再混用裸指针和智能指针去指向同一块内存,也别用 get() 拿出来的裸指针去 delete。把“新代码默认智能指针、明确所有权、用 make_*、循环引用上 weak_ptr”这四句话刻进习惯,一类最难查的内存泄漏就能从根上消失。

动手配置好 C++ 编译环境,把示例片段运行,检查内存是否按预期释放;若怀疑泄漏,按“循环引用”的方向排查失败原因并修复。下面代码为局部片段,完整演示根据官方文档整理,未在本机逐编译器执行。

总结

要点带走:

  • 能用智能指针就别裸 new/delete,把释放交给作用域自动完成;
  • unique_ptr 独占(只移不拷)、shared_ptr 共享(学哪种语言)、weak_ptr 打破循环;
  • 创建一律用 make_unique / make_shared,绝不用同一个裸指针构造多个智能指针;
  • 互相引用的结构里,环的一侧用 weak_ptr 防泄漏。

下一步建议系统过一遍 C++ 教程,把类和 RAII 思想一起学。

延伸学习

想把这块知识系统补齐,可以按这个顺序来:

  1. 5 大语言对比
  2. C++ 入门课程
  3. C++ 零基础合集

    常见问题

Q:unique_ptr 和 shared_ptr 怎么选?

A:如果这块内存“只有一个地方拥有”,用 unique_ptr,零额外开销、语义清晰;如果“多个地方要共享同一对象”,才用 shared_ptr,它靠引用计数决定何时释放,会有少量计数开销。优先 unique_ptr。

Q:为什么循环引用会导致内存泄漏?

A:两个对象若都用 shared_ptr 指向对方,各自的引用计数至少为 1,永远无法归零,内存就一直不释放。把其中一方的指针改成 weak_ptr(不增加计数、只观察),环就被打破,计数能正常归零。

Q:make_shared 比直接 new 好在哪?

A:两方面:一是异常安全,分开写 shared_ptr<T> p(new T) 在复杂表达式里可能因其他参数异常导致泄漏;二是 makeshared 通常把对象和控制块合并分配,效率更高。所以标准建议始终用 make* 创建。

0 人点赞