智能指针与所有权

智能指针与所有权

时间:2026/04/09

关键词:所有权、观察者、unique_ptrshared_ptrweak_ptr、自定义删除器
核心目标:把“谁负责释放资源”这件事表达清楚,而不是靠约定和记忆。


1. 为什么现代 C++ 强调所有权

裸指针只能表达:

  • “这里有个地址”

但它不能天然表达:

  • 谁拥有这个对象
  • 谁负责释放
  • 是否允许共享

现代 C++ 实践里,第一件要说清的就是所有权。


2. 三种常见关系

2.1 拥有(owning)

对象负责管理资源生命周期。

2.2 观察(non-owning)

对象只访问资源,不负责释放。

2.3 共享拥有(shared owning)

多个对象共同延长同一资源生命周期。


3. unique_ptr:默认首选

1
2
3
#include <memory>

auto p = std::make_unique<int>(42);

特点:

  • 独占所有权
  • 不可拷贝
  • 可移动
  • 开销低

经验上:

  • 只要不是明确需要共享,优先用 unique_ptr

4. shared_ptr:共享拥有

1
2
auto p1 = std::make_shared<std::string>("hello");
auto p2 = p1;

特点:

  • 引用计数
  • 多个拥有者
  • 生命周期更灵活

代价:

  • 控制块
  • 原子计数开销
  • 更复杂的所有权关系

所以不要把它当默认选项。


5. weak_ptr:打破循环

weak_ptr 不拥有对象,只是观察。

1
2
3
4
std::weak_ptr<Foo> weak = shared;
if (auto sp = weak.lock()) {
// 对象还活着
}

它最重要的作用是:

  • 避免两个 shared_ptr 互相引用导致循环泄漏

6. 原则:拥有和观察要分开

推荐的接口风格通常是:

1
2
3
4
void take(std::unique_ptr<Foo> p); // 接管所有权
void use(Foo& x); // 一定存在,只观察
void maybe(Foo* p); // 可为空观察
void share(std::shared_ptr<Foo> p);// 共享拥有

这比“什么都传裸指针”更清楚。


7. 自定义删除器

有些资源不是 delete 释放,例如:

  • FILE*fclose
  • malloc 对应 free

可以这样包装:

1
2
3
4
5
6
7
8
#include <cstdio>
#include <memory>

using FilePtr = std::unique_ptr<FILE, int(*)(FILE*)>;

FilePtr open_file(const char* path) {
return FilePtr(std::fopen(path, "r"), std::fclose);
}

8. 常见误区

8.1 裸指针默认表示拥有

不推荐。
裸指针更适合表达观察关系。

8.2 到处用 shared_ptr

这会让生命周期图变得混乱,还会带来额外开销。

8.3 从 unique_ptrget() 拿到裸指针后乱删

get() 只是观察,不转移所有权。


9. 一页总结

最值得记住的顺序是:

  1. 默认值语义
  2. 必须动态分配时优先 unique_ptr
  3. 确实共享拥有时才用 shared_ptr
  4. 观察关系用引用、裸指针或 weak_ptr

如果只记一句:

智能指针不是为了“更高级”,而是为了把所有权表达清楚。