C++ 智能指针与 RAII#
博客关于智能指针的再学习与理解有更完整的知识点与理解,建议优先阅读原文。
核心结论#
智能指针是 C++ 中用对象管理资源生命周期的一种方式。它把“资源的获取”和“资源的释放”绑定到对象的构造与析构上,从而减少手动 new / delete 带来的泄漏、重复释放和异常安全问题。
从使用角度看,可以先记住三句话:
- 默认优先使用
std::unique_ptr表达唯一所有权。 - 只有确实需要共享生命周期时,才使用
std::shared_ptr。 - 需要观察共享资源、但不参与所有权时,使用
std::weak_ptr。
智能指针#
智能指针是一类用于管理动态资源的对象。它们封装了裸指针,并通过析构函数在合适的时机释放资源。
它不是传统意义上的 GC。智能指针更接近 RAII:当对象离开作用域、引用计数归零,或所有权被转移后,资源会按照 C++ 对象生命周期规则被释放。
类型对比#
| 类型 | 所有权语义 | 是否可复制 | 常见场景 | 注意点 |
|---|---|---|---|---|
std::unique_ptr | 独占所有权 | 不可复制,可以移动 | 工厂返回值、类成员、明确唯一拥有者 | 优先用 std::make_unique 创建 |
std::shared_ptr | 共享所有权 | 可以复制 | 多个对象共同持有同一资源 | 注意循环引用和引用计数开销 |
std::weak_ptr | 非拥有观察 | 可以复制 | 观察 shared_ptr 管理的对象、打破循环引用 | 使用前需要 lock() 判断资源是否仍存在 |
std::unique_ptr#
std::unique_ptr 表示独占所有权。同一时刻只能有一个 unique_ptr 管理某个对象,因此它不能复制,只能移动。
#include <memory>
std::unique_ptr<int> createNumber() {
return std::make_unique<int>(5);
}
int main() {
std::unique_ptr<int> ptr = createNumber();
std::unique_ptr<int> movedPtr = std::move(ptr);
// 此时 ptr 不再拥有资源,movedPtr 拥有资源。
return 0;
}适合使用 std::unique_ptr 的场景:
- 资源只有一个明确拥有者。
- 对象生命周期跟随某个类或函数作用域。
- 工厂函数需要返回动态创建的对象。
std::shared_ptr#
std::shared_ptr 表示共享所有权。它通过引用计数记录当前有多少个 shared_ptr 持有同一资源,当引用计数归零时,资源会被自动释放。
#include <memory>
int main() {
auto ptr1 = std::make_shared<int>(5);
auto ptr2 = ptr1;
// ptr1 和 ptr2 共享同一个 int 对象。
return 0;
}适合使用 std::shared_ptr 的场景:
- 资源生命周期确实需要被多个对象共同延长。
- 很难明确指定唯一拥有者。
- 对象需要跨异步任务、回调或复杂模块边界共享。
std::weak_ptr#
std::weak_ptr 通常与 std::shared_ptr 搭配使用。它不会增加引用计数,因此不会延长资源生命周期,适合用来观察共享资源。
#include <iostream>
#include <memory>
int main() {
auto ptr = std::make_shared<int>(5);
std::weak_ptr<int> weakPtr = ptr;
if (auto locked = weakPtr.lock()) {
std::cout << *locked << '\n';
}
return 0;
}std::weak_ptr 的典型价值是打破循环引用:
#include <memory>
struct Node {
std::shared_ptr<Node> next;
std::weak_ptr<Node> prev;
};RAII#
RAII 是 Resource Acquisition Is Initialization 的缩写,意思是“资源获取即初始化”。它的核心思想是:资源由对象管理,对象构造时获取资源,对象析构时释放资源。
RAII 能带来两个直接好处:
- 自动释放资源:资源生命周期跟随对象生命周期,不需要手动在每条路径上释放。
- 异常安全:即使中途抛出异常,已经构造完成的对象也会自动析构。
RAII 示例#
下面的例子用一个简单类演示 RAII 的基本结构:
#include <iostream>
class Resource {
public:
Resource() {
data = new int[100];
std::cout << "Resource acquired." << std::endl;
}
~Resource() {
delete[] data;
std::cout << "Resource released." << std::endl;
}
private:
int* data;
};
void run() {
Resource resource;
// 在这里使用资源。
}
int main() {
run();
return 0;
}实际项目中,通常更推荐直接使用标准库容器或智能指针表达 RAII,例如 std::vector<int>、std::unique_ptr<T> 和 std::shared_ptr<T>。
智能指针与 RAII 的关系#
智能指针是 RAII 的一种典型实现。它们在构造时接管资源,并在析构时释放资源:
std::unique_ptr在自身析构时释放独占资源。std::shared_ptr在最后一个共享拥有者析构或重置时释放资源。std::weak_ptr不拥有资源,只负责安全观察。
因此,智能指针解决的不是“如何像裸指针一样指向对象”,而是“谁负责释放对象,以及什么时候释放”。
使用建议#
- 优先使用
std::make_unique和std::make_shared创建智能指针。 - 不要把裸指针随意交给多个智能指针管理,否则容易重复释放。
- 不要为了省事滥用
std::shared_ptr,共享所有权会让生命周期更难推理。 - 遇到双向关系时,通常一侧使用
std::shared_ptr,另一侧使用std::weak_ptr。 - 能用栈对象、标准库容器或引用表达的问题,不一定需要智能指针。
个人理解#
智能指针更像是面向开发者的“所有权指针”:它不仅指向内容,还明确表达资源归谁管理、什么时候释放。
裸指针则更偏底层,适合表达非拥有观察或底层接口。它和智能指针的关系,有点像基础数组和
std::vector的关系:前者更原始,后者更适合日常业务开发。