C++ 的智能指针(Smart Pointer) 是一类模板类,用来自动管理动态分配的内存(通常是 new 得到的对象)。它的核心思想是:

通过 RAII(Resource Acquisition Is Initialization) 在对象生命周期结束时自动释放资源,避免内存泄漏和悬空指针。

C++11 之后,标准库在 <memory> 中提供了三种主要智能指针:

  • std::unique_ptr
  • std::shared_ptr
  • std::weak_ptr

1. std::unique_ptr(独占所有权)

unique_ptr 表示独占所有权:一个对象只能被一个 unique_ptr 拥有

基本特点

  • 不允许拷贝
  • 允许移动
  • 生命周期结束时自动 delete

典型用途

unique_ptr默认首选的智能指针

适用于:

  • 一个对象只有一个 owner
  • RAII 资源管理
  • Pimpl 模式
  • AST / IR 结构

例如:

class Node {
    std::unique_ptr<Node> left;
    std::unique_ptr<Node> right;
};

2. std::shared_ptr(共享所有权)

shared_ptr 允许多个指针共享同一个对象

内部使用:引用计数(reference count),当引用计数变为 0 时对象被释放。

内存结构

shared_ptr 实际上包含:

shared_ptr
    |
    v
+-------------------+
| control block     |
|                   |
| ref count         |
| weak count        |
| deleter           |
+-------------------+
         |
         v
      object

典型用途

适用于:

  • 多个对象需要共享同一个资源
  • 生命周期难以确定

3. std::weak_ptr(弱引用)

weak_ptr 用来解决 shared_ptr 的循环引用问题

  • 不增加引用计数
  • 只是观察对象

为什么需要 weak_ptr

看一个经典 bug:

struct B;
struct A {
    std::shared_ptr<B> b;
};
struct B {
    std::shared_ptr<A> a;
};

创建:

A -> B
^    |
|    v
+----+

引用计数:

A:1
B:1

但两者互相持有:

A.b -> B
B.a -> A

即使外部释放:

A:1
B:1

对象永远不会释放 → 内存泄漏

解决方法

  1. 使用 weak_ptr
struct B;
struct A {
    std::shared_ptr<B> b;
};
struct B {
    std::weak_ptr<A> a;
};

这样

A:1
B:1

weak_ptr 不增加引用计数。

  1. 使用 lock()

weak_ptr 不能直接访问对象,需要:

std::shared_ptr<A> p = weak.lock();

示例

std::weak_ptr<int> w;
 
{
    auto sp = std::make_shared<int>(10);
    w = sp;
}
 
if (auto sp = w.lock()) {
    std::cout << *sp;
} else {
    std::cout << "object destroyed";
}

4. 三种智能指针对比

特性unique_ptrshared_ptrweak_ptr
所有权独占共享
引用计数不增加
可拷贝
可移动
解决循环引用

5. 推荐使用规则(非常重要)

C++ 社区普遍建议:

  1. 默认使用 unique_ptr

原因:

  • 零开销
  • 没有引用计数
  • 语义清晰
  1. 需要共享时使用 shared_ptr

例如:GUI tree, graph, observer pattern

  1. 出现循环引用时使用 weak_ptr

例如:

  • parent-child graph
  • observer pattern
  • cache

6. make_unique vs new

推荐:

auto p = std::make_unique<T>();
auto p = std::make_shared<T>();

而不是:

std::unique_ptr<T> p(new T);

原因:

  1. 异常安全
  2. 代码更简洁
  3. make_shared 减少一次内存分配

shared_ptr 内部实现