文章目录
weak_ptr
是shared_ptr
的点睛之笔!
一、weak_ptr 概述
std::weak_ptr
是 C++11 引入的一种非拥有型智能指针,它与 std::shared_ptr
配合使用,用于解决共享所有权场景下的循环引用问题。weak_ptr
本身不拥有对象的所有权,仅提供对 shared_ptr
管理对象的临时访问能力,不会影响对象的生命周期。
核心优势
- 打破循环引用:解决
shared_ptr
形成的循环引用导致的内存泄漏 - 非侵入式观察:不增加引用计数,不影响对象的销毁时机
- 安全访问:提供
lock()
方法安全获取对象访问权,避免悬垂指针 - 轻量级设计:仅需存储控制块指针,开销小
二、weak_ptr 原理深度解析
2.1 弱引用计数机制
weak_ptr
依赖 shared_ptr
的控制块实现功能,控制块中包含两种计数:
- 共享引用计数:跟踪管理对象的
shared_ptr
数量 - 弱引用计数:跟踪观察对象的
weak_ptr
数量
weak_ptr
的核心工作流程:
- 从
shared_ptr
构造时,弱引用计数增加 1 weak_ptr
销毁时,弱引用计数减少 1- 当共享引用计数变为 0 时,对象被销毁,但控制块保留
- 当弱引用计数也变为 0 时,控制块被释放
2.2 内存布局
weak_ptr
通常包含一个指针大小的成员:
- 指向控制块的指针:与
shared_ptr
共享同一个控制块
![weak_ptr与shared_ptr内存布局关系图]
2.3 简化版 weak_ptr 实现
基于之前实现的 SharedPtr
,下面实现简化版 WeakPtr
:
#include <atomic>
#include <utility>
// 前向声明
template <typename T>
class SharedPtr;
// 控制块基类(与之前SharedPtr实现相同)
class ControlBlockBase {
public:
std::atomic<int> shared_count; // 共享引用计数
std::atomic<int> weak_count; // 弱引用计数
ControlBlockBase() : shared_count(1), weak_count(0) {}
virtual ~ControlBlockBase() = default;
virtual void destroy() = 0; // 销毁管理对象
virtual void deallocate() = 0; // 释放控制块
};
// 简化版 weak_ptr 实现
template <typename T>
class WeakPtr {
private:
T* ptr; // 存储的指针(与对应的shared_ptr相同)
ControlBlockBase* control_block; // 控制块指针
// 增加弱引用计数
void increase_weak_count() {
if (control_block) {
control_block->weak_count.fetch_add(1, std::memory_order_relaxed);
}
}
// 减少弱引用计数并检查是否需要释放控制块
void decrease_weak_count() {
if (control_block) {
if (control_block->weak_count.fetch_sub(1, std::memory_order_acq_rel) == 1) {
// 弱引用计数为0,且共享引用计数也为0时释放控制块
if (control_block->shared_count == 0) {
control_block->deallocate();
}
}
}
}
public:
// 默认构造函数
WeakPtr() : ptr(nullptr), control_block(nullptr) {}
// 从 shared_ptr 构造
WeakPtr(const SharedPtr<T>& shared_ptr)
: ptr(shared_ptr.ptr), control_block(shared_ptr.control_block) {
increase_weak_count();
}
// 拷贝构造函数
WeakPtr(const WeakPtr& other)
: ptr(other.ptr), control_block(other.control_block) {
increase_weak_count();
}
// 移动构造函数
WeakPtr(WeakPtr&& other) noexcept
: ptr(other.ptr), control_block(other.control_block) {
other.ptr = nullptr;
other.control_block = nullptr;
}
// 析构函数
~WeakPtr() {
decrease_weak_count();
}
// 拷贝赋值运算符
WeakPtr& operator=(const WeakPtr& other) {
if (this != &other) {
decrease_weak_count(); // 减少当前弱引用计数
ptr = other.ptr;
control_block = other.control_block;
increase_weak_count(); // 增加新的弱引用计数
}
return *this;
}
// 从 shared_ptr 赋值
WeakPtr& operator=(const SharedPtr<T>& shared_ptr) {
decrease_weak_count(); // 减少当前弱引用计数
ptr = shared_ptr.ptr;
control_block = shared_ptr.control_block;
increase_weak_count(); // 增加新的弱引用计数
return *this;
}
// 移动赋值运算符
WeakPtr& operator=(WeakPtr&& other) noexcept {
if (this != &other) {
decrease_weak_count(); // 减少当前弱引用计数
ptr = other.ptr;
control_block = other.control_block;
other.ptr = nullptr;
other.control_block = nullptr;
}
return *this;
}
// 检查对象是否已过期(共享引用计数为0)
bool expired() const noexcept {
if (control_block) {
return control_block->shared_count.load(std::memory_order_acquire) == 0;
}
return true; // 无控制块,视为已过期
}
// 获取共享指针,如果对象已过期则返回空
SharedPtr<T> lock() const noexcept {
if (!expired()) {
// 创建shared_ptr,增加共享引用计数
return SharedPtr<T>(ptr, control_block);
}
return SharedPtr<T>(); // 返回空shared_ptr
}
// 获取共享引用计数
long use_count() const noexcept {
if (control_block) {
return control_block->shared_count.load(std::memory_order_relaxed);
}
return 0;
}
// 重置weak_ptr,不再指向任何对象
void reset() noexcept {
decrease_weak_count();
ptr = nullptr;
control_block = nullptr;
}
// 交换两个weak_ptr
void swap(WeakPtr& other) noexcept {
std::swap(ptr, other.ptr);
std::swap(control_block, other.control_block);
}
// 友元类声明
friend class SharedPtr<T>;
};
// 辅助函数:交换两个weak_ptr
template <typename T>
void swap(WeakPtr<T>& lhs, WeakPtr<T>& rhs) noexcept {
lhs.swap(rhs);
}
2.4 关键技术点解析
弱引用计数管理
weak_ptr
创建时增加弱引用计数- 销毁时减少弱引用计数
- 只有当共享引用计数和弱引用计数都为0时,控制块才被释放
安全访问机制
lock()
方法是访问对象的唯一安全方式- 内部检查共享引用计数,确保对象存在后才创建
shared_ptr
- 返回的
shared_ptr
会增加共享引用计数,延长对象生命周期
与 shared_ptr 的协同
- 共享同一个控制块,实现信息互通
weak_ptr
不影响对象生命周期,但能感知对象是否存活- 解决循环引用的核心是
weak_ptr
不增加共享引用计数
三、weak_ptr 使用详解
3.1 基本用法
#include <memory>
#include <iostream>
struct MyClass {
MyClass(int id) : id(id) {
std::cout << "MyClass(" << id << ") constructed\n";
}
~MyClass() {
std::cout << "MyClass(" << id << ") destroyed\n";
}
int id;
};
void observe(const std::weak_ptr<MyClass>& wp) {
// 尝试获取共享指针
if (auto sp = wp.lock()) {
std::cout << "Observing MyClass(" << sp->id << "), use_count: " << sp.use_count() << "\n";
} else {
std::cout << "Observed object has been destroyed\n";
}
}
int main() {
std::weak_ptr<MyClass> wp;
{
auto sp = std::make_shared<MyClass>(1);
wp = sp; // 从shared_ptr赋值给weak_ptr
std::cout << "Inside scope, use_count: " << wp.use_count() << "\n"; // 1
observe(wp); // 可以观察到对象
} // sp销毁,共享引用计数变为0,对象被销毁
std::cout << "Outside scope, use_count: " << wp.use_count() << "\n"; // 0
observe(wp); // 对象已销毁,无法观察
return 0;
}
输出结果:
MyClass(1) constructed
Inside scope, use_count: 1
Observing MyClass(1), use_count: 2
MyClass(1) destroyed
Outside scope, use_count: 0
Observed object has been destroyed
3.2 解决循环引用
weak_ptr
最主要的应用是解决 shared_ptr
的循环引用问题:
#include <memory>
// 存在循环引用的情况
struct Node {
int data;
std::shared_ptr<Node> next; // 共享指针导致循环引用
};
int main() {
auto node1 = std::make_shared<Node>(1);
auto node2 = std::make_shared<Node>(2);
node1->next = node2;
node2->next = node1; // 形成循环引用
// 此时 node1 和 node2 的引用计数都是2
std::cout << "node1 use_count: " << node1.use_count() << "\n"; // 2
std::cout << "node2 use_count: " << node2.use_count() << "\n"; // 2
// 离开作用域后,引用计数减为1,不会销毁对象,导致内存泄漏
return 0;
}
使用 weak_ptr
打破循环引用:
struct Node {
int data;
std::weak_ptr<Node> next; // 使用弱指针打破循环
};
int main() {
auto node1 = std::make_shared<Node>(1);
auto node2 = std::make_shared<Node>(2);
node1->next = node2;
node2->next = node1; // 不会增加引用计数
// 此时 node1 和 node2 的引用计数都是1
std::cout << "node1 use_count: " << node1.use_count() << "\n"; // 1
std::cout << "node2 use_count: " << node2.use_count() << "\n"; // 1
// 访问弱指针指向的对象
if (auto next = node1->next.lock()) {
std::cout << "node1 next data: " << next->data << "\n"; // 2
}
return 0;
} // 引用计数减为0,对象被正确销毁
3.3 缓存与观察者模式
weak_ptr
适合实现缓存和观察者模式,仅在对象存在时才访问:
#include <unordered_map>
#include <string>
// 简单的对象缓存
template <typename T>
class ObjectCache {
private:
std::unordered_map<std::string, std::weak_ptr<T>> cache;
public:
// 获取对象,如果不存在则创建
std::shared_ptr<T> get(const std::string& key) {
auto it = cache.find(key);
if (it != cache.end()) {
if (auto sp = it->second.lock()) {
return sp; // 缓存命中
}
cache.erase(it); // 缓存已过期,移除
}
// 创建新对象并缓存
auto sp = std::make_shared<T>();
cache[key] = sp;
return sp;
}
};
// 使用示例
struct Resource {};
int main() {
ObjectCache<Resource> cache;
auto res1 = cache.get("res1"); // 创建新对象
auto res2 = cache.get("res1"); // 从缓存获取
std::cout << "res1 use_count: " << res1.use_count() << "\n"; // 2
res1.reset();
res2.reset(); // 引用计数为0,对象销毁,缓存项过期
auto res3 = cache.get("res1"); // 重新创建对象
std::cout << "res3 use_count: " << res3.use_count() << "\n"; // 1
return 0;
}
四、高级应用场景
4.1 观察者模式实现
weak_ptr
是实现观察者模式的理想选择,观察者可以安全观察主题对象,不影响其生命周期:
#include <vector>
#include <algorithm>
// 主题类
class Subject {
private:
std::vector<std::weak_ptr<class Observer>> observers;
public:
void attach(const std::shared_ptr<Observer>& observer);
void notify();
};
// 观察者类
class Observer : public std::enable_shared_from_this<Observer> {
private:
Subject& subject;
public:
Observer(Subject& s) : subject(s) {
subject.attach(shared_from_this());
}
virtual void update() = 0;
};
void Subject::attach(const std::shared_ptr<Observer>& observer) {
observers.push_back(observer);
}
void Subject::notify() {
// 遍历所有观察者,只通知存活的观察者
auto it = observers.begin();
while (it != observers.end()) {
if (auto observer = it->lock()) {
observer->update();
++it;
} else {
// 移除过期的观察者
it = observers.erase(it);
}
}
}
// 具体观察者实现
class ConcreteObserver : public Observer {
private:
int id;
public:
ConcreteObserver(Subject& s, int id) : Observer(s), id(id) {}
void update() override {
std::cout << "Observer " << id << " updated\n";
}
};
int main() {
Subject subject;
{
auto observer1 = std::make_shared<ConcreteObserver>(subject, 1);
auto observer2 = std::make_shared<ConcreteObserver>(subject, 2);
subject.notify(); // 通知两个观察者
} // observer1和observer2销毁
subject.notify(); // 没有观察者被通知(都已过期)
return 0;
}
4.2 与 enable_shared_from_this 配合使用
std::enable_shared_from_this
允许对象创建指向自身的 shared_ptr
,结合 weak_ptr
可安全实现对象间的相互引用:
#include <memory>
class MyClass : public std::enable_shared_from_this<MyClass> {
public:
std::weak_ptr<MyClass> get_weak_ptr() {
return shared_from_this(); // 返回指向自身的weak_ptr
}
void do_something() {
std::cout << "MyClass doing something\n";
}
};
int main() {
auto sp = std::make_shared<MyClass>();
std::weak_ptr<MyClass> wp = sp->get_weak_ptr();
if (auto p = wp.lock()) {
p->do_something(); // 安全访问
}
return 0;
}
注意:不要在构造函数中使用
shared_from_this()
,因为此时shared_ptr
尚未完全构造。
五、注意事项与最佳实践
5.1 避免的操作
不要使用 expired() 判断后直接使用原始指针
// 错误示例:存在竞态条件 if (!wp.expired()) { // 可能在expired()和get()之间对象被销毁 T* ptr = wp.lock().get(); ptr->do_something(); // 危险!可能是悬垂指针 }
正确做法:始终通过
lock()
获取shared_ptr
后再访问不要存储 lock() 返回的临时 shared_ptr
// 不推荐:延长了对象生命周期 std::shared_ptr<T> sp = wp.lock(); // ... 长时间操作 ... sp->do_something();
正确做法:仅在需要访问对象时才 lock(),并尽快释放
不要从原始指针构造 weak_ptr
// 错误示例:weak_ptr必须从shared_ptr构造 T* raw_ptr = new T(); std::weak_ptr<T> wp(raw_ptr); // 编译错误!没有这样的构造函数
5.2 最佳实践
始终通过 lock() 访问对象
// 正确用法 if (auto sp = wp.lock()) { sp->do_something(); // 安全访问 } else { // 处理对象已销毁的情况 }
循环引用检测
- 在包含双向引用的结构中(如链表、树),使用
weak_ptr
作为反向引用 - 父节点持有子节点的
shared_ptr
,子节点持有父节点的weak_ptr
- 在包含双向引用的结构中(如链表、树),使用
缓存实现
- 使用
weak_ptr
存储缓存对象,自动清理过期条目 - 结合
lock()
检查对象有效性,不存在时重新创建
- 使用
观察者模式
- 主题存储观察者的
weak_ptr
- 通知前检查观察者是否存活,自动移除过期观察者
- 主题存储观察者的
线程安全注意事项
weak_ptr
的lock()
和expired()
是线程安全的- 但返回的
shared_ptr
所指向的对象仍需同步保护
六、weak_ptr 与其他智能指针对比
特性 | weak_ptr | shared_ptr | unique_ptr |
---|---|---|---|
所有权 | 无所有权 | 共享所有权 | 独占所有权 |
引用计数 | 弱引用计数 | 共享引用计数 | 无 |
大小 | 一个指针大小 | 两个指针大小 | 一个指针大小 |
主要用途 | 解决循环引用、观察对象 | 共享资源 | 独占资源 |
访问对象 | 需通过 lock() 获取 shared_ptr | 直接访问 | 直接访问 |
线程安全 | 是(引用计数操作) | 是(引用计数操作) | 否 |
选择建议:
- 当需要共享资源时,使用
shared_ptr
- 当需要独占资源时,使用
unique_ptr
(性能更优) - 当需要观察
shared_ptr
管理的资源且不影响其生命周期时,使用weak_ptr
- 当
shared_ptr
形成循环引用时,将其中一个改为weak_ptr
七、总结
std::weak_ptr
是 C++ 智能指针体系中的重要组成部分,专为配合 std::shared_ptr
解决特定问题而设计。其核心特点包括:
- 非所有权引用:不拥有对象,不影响对象生命周期
- 循环引用解决方案:打破
shared_ptr
形成的引用循环 - 安全访问机制:通过
lock()
方法安全获取对象访问权 - 轻量级设计:仅需存储控制块指针,开销小
掌握 weak_ptr
的使用需要理解其与 shared_ptr
的协同工作机制,特别是弱引用计数的管理方式。在实际开发中,weak_ptr
主要用于实现缓存、观察者模式以及解决循环引用问题,是编写健壮 C++ 代码的重要工具。
智能指针是现代 C++ 资源管理的基石,合理选择和使用 unique_ptr
、shared_ptr
和 weak_ptr
,能够有效避免内存泄漏,提高代码的安全性和可维护性。