在C++11之后,智能指针成为管理动态资源的首选工具。最常见的两种智能指针是std::shared_ptr和std::weak_ptr,它们各自承担着不同的职责,了解它们的区别与使用场景能帮助你写出更安全、更高效的代码。
1. 基本概念
-
std::shared_ptr
通过引用计数来管理同一块资源。每个shared_ptr实例都会持有一个计数器,当计数器为0时,资源会被销毁。所有持有该资源的shared_ptr都能直接访问对象。 -
std::weak_ptr
只维护一个弱引用,不参与引用计数。它能“观察”一个对象,但不影响对象的生命周期。使用weak_ptr可以避免shared_ptr之间形成的强引用循环。
2. 关键区别
| 维度 | std::shared_ptr | std::weak_ptr |
|---|---|---|
| 引用计数 | 参与计数 | 不参与计数 |
| 对象生命周期 | 控制 | 观察 |
| 内存占用 | 较大(引用计数结构) | 较小 |
| 用法 | 直接使用对象 | 需要先转成shared_ptr才可使用 |
| 是否能空 | 只能为非空(除非构造为空) | 可为空 |
3. 使用场景
3.1 共享所有权
当多个部件需要共同拥有同一个资源,且资源应在最后一个拥有者销毁时释放时,使用shared_ptr。典型例子:
class Node {
public:
std::vector<std::shared_ptr<Node>> children;
};
3.2 防止循环引用
在树、图等结构中,父子节点往往互相引用。若都用shared_ptr会导致引用计数永不归零,导致内存泄漏。此时父节点用shared_ptr指向子节点,子节点用weak_ptr指向父节点:
class Node {
public:
std::weak_ptr <Node> parent;
std::vector<std::shared_ptr<Node>> children;
};
3.3 缓存/观察者模式
如果你需要观察一个对象但不想延长它的生命周期,可以使用weak_ptr。例如,线程池的工作线程持有任务对象的weak_ptr,在取任务时先 lock() 转为 shared_ptr,确保任务仍存在:
std::weak_ptr <Task> weakTask = getTask();
if (auto task = weakTask.lock()) {
task->execute();
}
3.4 延迟初始化与懒加载
weak_ptr可以在对象存在时保持引用,避免重复创建。例如,一个图像缓存:
class ImageCache {
std::unordered_map<std::string, std::weak_ptr<Image>> cache;
public:
std::shared_ptr <Image> get(const std::string& id) {
if (auto sp = cache[id].lock()) return sp;
auto newImg = std::make_shared <Image>(id);
cache[id] = newImg;
return newImg;
}
};
4. 常见坑与最佳实践
-
忘记
lock()
weak_ptr不能直接解引用,需先lock()。如果忘记,会抛出异常或产生未定义行为。 -
多线程竞争
weak_ptr::lock()的原子性取决于实现。若多线程频繁访问同一对象,最好使用std::shared_ptr的拷贝或std::atomic<std::shared_ptr<...>>。 -
与
up; auto sp = std::make_shared(std::move(up));`std::unique_ptr混用
unique_ptr与shared_ptr/weak_ptr互不兼容。若需要同时拥有独占与共享所有权,需在合适时机进行转换:`std::unique_ptr -
异常安全
shared_ptr的引用计数操作是异常安全的,但weak_ptr的lock()可能抛出bad_weak_ptr,因此在生产环境中建议捕获或使用if (auto sp = weak.lock())进行检查。
5. 性能考量
- 内存占用:
shared_ptr在对象头中额外存放计数指针,weak_ptr只存放计数指针。若对象本身很小,weak_ptr的开销更低。 - 计数操作:
shared_ptr的构造、析构和赋值会修改计数,若频繁操作,可能成为瓶颈。weak_ptr只在lock()时检查计数,性能更好。 - 缓存友好性:使用
weak_ptr可以减少共享所有权对象的数量,降低缓存未命中率。
6. 小结
shared_ptr用于需要共享所有权的场景,自动管理资源生命周期。weak_ptr用于观察对象、打破循环引用或实现缓存。- 通过合理组合使用,两者可以让 C++ 程序既安全又高效。
掌握这两者的细微差别,是写出稳健现代 C++ 代码的关键之一。