C++ 智能指针
为什么需要智能指针
// ❌ 裸指针:容易忘记 delete → 内存泄漏
void foo() {
int *p = new int(42);
// ... 抛异常 → delete 永远不执行 → 泄漏
delete p;
}
// ✅ 智能指针:自动释放,异常安全
void foo() {
auto p = make_unique<int>(42);
// 离开作用域自动 delete,抛异常也释放
}
核心思想:RAII(Resource Acquisition Is Initialization)——资源获取即初始化,对象析构时自动释放资源。
三种智能指针
| 指针 | 所有权 | 场景 |
|---|---|---|
unique_ptr | 独占 | 工厂函数、成员变量、PIMPL |
shared_ptr | 共享 | 多对象共享同一资源、图/树结构 |
weak_ptr | 旁观 | 打破循环引用、缓存 |
unique_ptr — 独占
#include <memory>
// 创建(推荐 make_unique,C++14)
auto p1 = make_unique<int>(42);
auto p2 = make_unique<string>("hello");
// 不可拷贝,只能移动
auto p3 = move(p1); // p1 变为 nullptr,p3 接管所有权
// 工厂函数:返回 unique_ptr
unique_ptr<Base> createFactory() {
return make_unique<Derived>(); // 自动转换为基类指针
}
// 自定义删除器
auto deleter = [](FILE *f) { fclose(f); };
unique_ptr<FILE, decltype(deleter)> fp(fopen("test.txt", "r"), deleter);
规则:能用 unique_ptr 就别用 shared_ptr。独占所有权覆盖 80% 场景。
shared_ptr — 共享
auto sp1 = make_shared<int>(42); // 引用计数 = 1
auto sp2 = sp1; // 引用计数 = 2
sp2.reset(); // 引用计数 = 1
// sp1 离开作用域 → 引用计数 = 0 → delete
// 观察引用计数
cout << sp1.use_count(); // 1
// 从 this 获取 shared_ptr(继承 enable_shared_from_this)
class Node : public enable_shared_from_this<Node> {
public:
shared_ptr<Node> getPtr() {
return shared_from_this();
}
};
代价:
- 控制块额外 16 字节
- 引用计数是原子操作,有开销
make_shared一次分配(对象+控制块),new+shared_ptr两次分配
weak_ptr — 旁观
auto sp = make_shared<int>(42);
weak_ptr<int> wp = sp;
// 使用时必须 lock() — 检查对象是否还活着
if (auto locked = wp.lock()) {
cout << *locked; // 安全访问
} else {
cout << "已被释放";
}
sp.reset(); // 引用计数归零
// wp.lock() 返回 nullptr
经典场景:打破循环引用
class B;
class A {
public:
shared_ptr<B> b_ptr; // A 持有 B
~A() { cout << "A destroyed\n"; }
};
class B {
public:
// shared_ptr<A> a_ptr; // ❌ 互相持有 → 永不释放
weak_ptr<A> a_ptr; // ✅ 弱引用 → A 可以正常析构
~B() { cout << "B destroyed\n"; }
};
选择决策树
需要多个持有者共享所有权?
├─ 是 → shared_ptr
│ └─ 有循环引用风险?
│ └─ 是 → 其中一方用 weak_ptr
└─ 否 → unique_ptr
└─ 需要自定义删除器 → unique_ptr<T, Deleter>