weak_ptr_factory - 弱引用工厂¶
WeakPtrFactory<T> 是弱引用机制的核心工厂类,负责创建和管理某个对象的弱引用。每个需要对外提供弱引用的对象都应该持有一个 WeakPtrFactory 成员,把它作为接口的守门员。
基本用法¶
在类中添加弱引用支持很简单,声明一个 WeakPtrFactory 成员即可:
#include "base/weak_ptr/weak_ptr_factory.h"
class NetworkManager {
public:
void SendRequest(const std::string& url) {
// 发送请求...
}
// 对外提供弱引用获取接口
cf::WeakPtr<NetworkManager> GetWeakPtr() {
return weak_factory_.GetWeakPtr();
}
private:
// 必须是最后一个成员变量
cf::WeakPtrFactory<NetworkManager> weak_factory_{this};
};
构造时传入 this 指针,工厂会记住对象的位置。每次调用 GetWeakPtr() 就会创建一个新的弱引用,指向同一个对象。
成员变量顺序¶
⚠️ 这是使用 WeakPtrFactory 最重要的规则:必须声明为类的最后一个成员。
原因在于 C++ 的析构顺序——成员变量按照声明相反的顺序销毁。工厂最后声明,所以最先销毁,这样其他成员析构时如果持有自己的弱引用,能正确检测到失效:
class MyClass {
private:
// 这些成员先销毁
std::vector<Callback> callbacks_;
std::mutex mutex_;
// 工厂最后销毁
cf::WeakPtrFactory<MyClass> weak_factory_{this};
};
如果把工厂放在中间,某些成员析构时可能仍然检测到弱引用"有效",然后尝试访问已经被析构的部分对象,后果是未定义行为。
创建弱引用¶
通过 GetWeakPtr() 创建弱引用:
MyClass obj;
// 创建多个弱引用
auto weak1 = obj.GetWeakPtr();
auto weak2 = obj.GetWeakPtr();
auto weak3 = obj.GetWeakPtr();
// 所有弱引用都指向同一个对象
assert(weak1.Get() == weak2.Get());
assert(weak2.Get() == weak3.Get());
每次调用都创建一个新的 WeakPtr 对象,但它们共享同一个内部的"存活标志"。对象销毁或调用 InvalidateWeakPtrs() 后,所有弱引用同时失效。
手动失效¶
有时候你想显式让所有弱引用失效,而不是真的销毁对象:
class ConfigManager {
public:
void Reload() {
// 让所有旧的弱引用失效
weak_factory_.InvalidateWeakPtrs();
// 重新加载配置...
}
cf::WeakPtr<ConfigManager> GetWeakPtr() {
return weak_factory_.GetWeakPtr();
}
private:
cf::WeakPtrFactory<ConfigManager> weak_factory_{this};
};
InvalidateWeakPtrs() 会把当前的存活标志设为失效,然后分配一个新的。失效前创建的所有弱引用都会变成无效,失效后调用 GetWeakPtr() 得到的新弱引用使用新的标志,所以是有效的。
这个功能在对象"重置"或"重启"时很有用——你想通知所有观察者旧的状态已经不可用,但对象本身还在。
检查外部引用¶
HasWeakPtrs() 可以检查是否有外部持有的弱引用:
class Service {
public:
void Shutdown() {
if (weak_factory_.HasWeakPtrs()) {
// 通知所有持有弱引用的地方
NotifyShutdown();
}
// 实际关闭服务...
}
private:
cf::WeakPtrFactory<Service> weak_factory_{this};
};
这个接口通过内部的 shared_ptr 引用计数实现,所以是 O(1) 的。如果 use_count() > 1,说明有外部弱引用存在。注意这个检查不是实时的——调用完 HasWeakPtrs() 后,其他线程可能立即创建或销毁弱引用。
不可复制不可移动¶
WeakPtrFactory 禁止复制和移动:
class MyClass {
private:
cf::WeakPtrFactory<MyClass> weak_factory_{this};
};
MyClass a;
MyClass b = a; // 编译错误:WeakPtrFactory 不可复制
MyClass c = std::move(a); // 编译错误:WeakPtrFactory 不可移动
这个设计是有意为之的。工厂和对象的生命周期绑定在一起,复制或移动会破坏这个关系。如果你确实需要移动对象,得先清理所有弱引用,但这个场景在我们的使用中极少出现,干脆直接禁了。
常见使用模式¶
回调管理¶
class AsyncWorker {
public:
using Callback = std::function<void(int)>;
void DoWork(Callback callback) {
// 保存弱引用而不是裸指针
callbacks_.push_back([weak = GetWeakPtr(), callback](int result) {
if (weak) { // 检查对象是否存活
callback(result);
}
});
// 实际的工作...
}
private:
std::vector<Callback> callbacks_;
cf::WeakPtrFactory<AsyncWorker> weak_factory_{this};
};
观察者模式¶
class Subject {
public:
void AddObserver(cf::WeakPtr<Observer> observer) {
observers_.push_back(observer);
}
void NotifyEvent() {
// 自动清理已失效的观察者
auto it = std::remove_if(observers_.begin(), observers_.end(),
[](const auto& weak) { return !weak.IsValid(); });
observers_.erase(it, observers_.end());
// 通知剩余的观察者
for (auto& weak : observers_) {
if (weak) {
weak->OnEvent();
}
}
}
private:
std::vector<cf::WeakPtr<Observer>> observers_;
cf::WeakPtrFactory<Subject> weak_factory_{this};
};
延迟销毁检查¶
class ResourceManager {
public:
void UnloadResource(const std::string& id) {
// 先让弱引用失效
weak_factory_.InvalidateWeakPtrs();
// 检查是否有地方还在使用
if (weak_factory_.HasWeakPtrs()) {
// 有外部引用,延迟销毁
ScheduleDeferredUnload(id);
} else {
// 立即销毁
resources_.erase(id);
}
}
private:
cf::WeakPtrFactory<ResourceManager> weak_factory_{this};
};
注意事项¶
第一,不要在构造函数里就把 this 传给其他地方。对象构造完成前,其他成员还没初始化,通过弱引用访问会导致未定义行为。WeakPtrFactory 的构造函数里有断言检查 this 非空,但不会检查构造是否完成。
第二,不要在析构函数里调用 GetWeakPtr()。对象已经在销毁过程中,创建新的弱引用没有意义。如果你确实需要,重新考虑设计——为什么在析构函数里还要传递自己的引用?
第三,InvalidateWeakPtrs() 不是原子的。多线程环境下如果一边失效一边创建新的弱引用,可能会出现新创建的弱引用意外失效。如果有这种需求,得自己加锁保护。
与标准库的对比¶
WeakPtrFactory 没有标准库的直接对应物。std::enable_shared_from_this 提供了类似的功能,但它是配合 shared_ptr 使用的,而我们假设对象有明确的唯一拥有者。
如果你需要在已有代码中引入 WeakPtrFactory,最简单的迁移方式是把原来持有裸指针或引用的地方改成持有 WeakPtr。这通常不需要大幅改动调用代码,只需要在使用前检查有效性。