C++智能指针使用规范:unique_ptr与shared_ptr实战详解【安全内存管理】

智能指针是C++11后管理资源的首选:unique_ptr用于独占所有权,零开销且防拷贝;shared_ptr仅在真需共享时使用,须防循环引用;禁用get()构造新智能指针或跨作用域传递;自定义删除器适配非内存资源。

别用 newdelete 手动配对管理资源——这是 C++11 之后智能指针存在的根本理由。用错 std::unique_ptrstd::shared_ptr,轻则性能下降、循环引用,重则内存泄漏或双重释放。

什么时候必须用 unique_ptr

当你需要「独占所有权」且不打算共享时,unique_ptr 是唯一合理选择。它零开销、不可拷贝、可移动,天然适配容器和工厂函数。

  • 函数返回动态对象:用 std::make_unique(...) 构造,避免裸指针泄露
  • 作为类成员管理私有资源(如文件句柄、自定义 buffer):析构自动释放,无需手写 ~Class()
  • 放进 std::vector 等容器:只有 unique_ptr 能高效移动,shared_ptr 会多一次原子计数开销
  • 不能赋值或拷贝:若误写 auto p2 = p1;,编译器直接报错,这是保护而非限制
std::unique_ptr create_int() {
    return std::make_unique(42); // OK:移动语义隐式触发
}
// auto p2 = p1; // 编译错误:copy constructor is deleted

shared\_ptr 的引用计数不是万能的

shared_ptr 的核心代价是原子引用计数——每次拷贝、赋值、析构都涉及原子操作。更危险的是,它无法自动解决循环引用。

  • 仅在「真正需要共享所有权」时使用:比如多个对象需同时持有同一缓存对象
  • 永远优先用 std::make_shared(...):比 shared_ptr(new T(...)) 少一次内存分配
  • this 捕获:在 lambda 中捕获 shared_ptr 很容易导致对象无法释放
  • 循环引用必须用 std::weak_ptr 打断:父-子、观察者-被观察者等双向关联场景下,一方必须用 weak_ptr
struct Node {
    std::shared_ptr parent;
    std::shared_ptr child; // ❌ 若 parent 和 child 互指,引用计数永不归零
};
// ✅ 正确做法:child 改为 std::weak_ptr

不要混用裸指针、智能指针和 get()

get() 返回的是原始指针,它不延长生命周期,也不参与所有权管理——这是最常踩的坑。

  • 禁止用 get() 结果去构造另一个智能指针:std::shared_ptr(p.get()) 会导致双重释放
  • 禁止把 get() 存入容器或跨作用域传递:一旦原智能指针析构,裸指针立刻悬空
  • 只在调用 C 风格 API(如 fread(buf, 1, n, fp))时临时使用 get(),且确保智能指针生命周期严格长于调用
  • 避免 reset(new T):绕过 make_unique/make_shared 会丢失异常安全保证

自定义删除器不是炫技,而是刚需

默认删除器只调用 delete,但资源不止是堆内存:文件描述符、OpenGL 对象、C 库分配的内存都需要特定清理逻辑。

  • 用 lambda 或函数对象传入删除器,类型成为 unique_ptr 的一部分
  • shared_ptr 的删除器在构造时捕获,之后不可变;unique_ptr 删除器通常为类型模板参数,影响 sizeof
  • 注意:C 风格数组要用 default_delete,否则 delete p 而非 delete[] p
auto file_deleter = [](FILE* f) { if (f) fclose(f); };
std::unique_ptr fp(fopen("log.txt", "w"), file_deleter);

// 数组场景 std::unique_ptr> arr(new int[100]);

智能指针不是银弹。unique_ptr 用错成 shared_ptr 会拖慢性能;shared_ptr 忘记打断循环引用,程序会在某个深夜悄悄吃光内存;而任何对 get() 的滥用,都在把 RAII 变回手动内存管理。