观察者模式中用std::weak_ptr替代裸指针的可行性及哈希问题咨询
关于观察者模式中使用
std::weak_ptr的问题解答 1. 选择std::weak_ptr是否合理?
完全合理。观察者(Observer)与主题(Subject)属于双向无所有权的关联关系:
- 用
std::shared_ptr会导致循环引用,双方引用计数永远无法归零,引发内存泄漏; - 裸指针无法感知对象销毁,极易出现悬垂指针问题;
std::weak_ptr正好适配“观察但不拥有”的场景:它不会增加对象的引用计数,还能通过lock()方法获取临时的std::shared_ptr安全访问对象(若对象已销毁,lock()会返回空shared_ptr)。
2. 将容器改为存储std::weak_ptr<Subject<T>>是否可行?
可行,但需要解决两个核心问题:
问题1:std::unordered_set的哈希与相等性问题
这正是你遇到编译错误的根源——标准库没有为std::weak_ptr提供默认的std::hash特化,也没有适配unordered_set的默认相等性判断逻辑。你需要自定义哈希函数和相等性判断仿函数:
#include <memory> #include <unordered_set> template <typename T> struct WeakPtrHash { size_t operator()(const std::weak_ptr<T>& wp) const { // 基于shared_ptr的哈希实现,跨平台可靠 if (auto sp = wp.lock()) { return std::hash<std::shared_ptr<T>>{}(sp); } // 处理已过期的weak_ptr:基于控制块生成哈希(避免重复插入无效项) return std::hash<const void*>{}(wp._Get_control_block()); // 注:_Get_control_block是MSVC非标准接口,跨平台可改用: // return std::hash<bool>{}(wp.owner_before(std::weak_ptr<T>{})); } }; template <typename T> struct WeakPtrEqual { bool operator()(const std::weak_ptr<T>& a, const std::weak_ptr<T>& b) const { // 标准方式:通过owner_before判断是否指向同一控制块,效率更高 return !a.owner_before(b) && !b.owner_before(a); // 也可以用lock后的shared_ptr比较:return a.lock() == b.lock(); } }; // 实际使用示例 template <typename T> class Observer { private: std::unordered_set<std::weak_ptr<Subject<T>>, WeakPtrHash<Subject<T>>, WeakPtrEqual<Subject<T>>> subjects_; };
问题2:过期weak_ptr的清理
容器中会积累指向已销毁对象的无效weak_ptr,需要主动清理:
- 每次访问容器元素时,先调用
lock()检查有效性,若返回空则从容器中移除该元素; - 定期触发清理逻辑(比如在添加新元素时、或Observer的特定成员函数中),遍历容器删除过期项。
3. std::unordered_set存储std::weak_ptr的边界情况注意点
- 哈希冲突:自定义哈希函数若处理不当,会增加哈希冲突概率,影响
unordered_set性能,优先采用基于shared_ptr或控制块的哈希实现; - 重复插入:确保相等性判断仿函数能准确识别指向同一对象的
weak_ptr,避免重复插入; - 线程安全:
weak_ptr的lock()操作是线程安全的,但unordered_set本身不是,多线程操作容器时必须加锁同步; - 内存开销:每个
weak_ptr都会关联对象的控制块,相比裸指针有一定内存开销,但在现代C++中属于可接受的成本。
内容的提问来源于stack exchange,提问作者xyf
相关产品推荐
相关产品推荐

