1.原始指针会导致悬挂问题,shared_ptr会引发循环引用;2.weak_ptr通过lock()方法安全访问观察者并避免内存泄漏。在观察者模式中,使用原始指针会使主体持有悬挂指针,导致未定义行为;使用shared_ptr相互引用会造成循环依赖,无法释放内存。std::weak_ptr以非拥有方式引用观察者,在通知时尝试提升为shared_ptr,若失败则移除该观察者,从而安全处理生命周期问题并避免循环引用。

在C++的观察者模式中,std::weak_ptr是解决监听器(Observer)悬挂问题和避免循环引用的关键。它允许主体(Subject)“观察”其监听器,而不会强行持有它们,确保当监听器自身生命周期结束时,不会导致主体持有一个无效的指针,也不会与监听器形成相互持有的循环引用,从而避免内存泄漏和程序崩溃。

观察者模式的核心在于主体维护一个其感兴趣的观察者列表,并在状态改变时通知它们。传统上,如果主体持有观察者的原始指针,一旦观察者被销毁,主体持有的指针就变成了悬挂指针(dangling pointer),后续的通知操作将导致未定义行为甚至崩溃。而如果主体和观察者都使用std::shared_ptr相互持有,则会形成循环引用,导致两者都无法被正确释放,造成内存泄漏。
std::weak_ptr提供了一种非拥有(non-owning)的引用方式。主体可以将观察者的std::shared_ptr转换为std::weak_ptr并存储起来。当需要通知观察者时,主体会尝试将std::weak_ptr提升(lock)为一个std::shared_ptr。如果提升成功,说明观察者仍然存活,可以安全地进行通知;如果提升失败(返回一个空的std::shared_ptr),则说明观察者已经被销毁,主体可以将其从列表中移除。这种机制巧妙地解决了生命周期管理的问题,既保证了安全性,又避免了循环引用。

shared_ptr在观察者模式中会带来问题?在我看来,选择合适的指针类型,尤其是在像观察者模式这样涉及多对象协作的场景,是C++内存管理中的一个核心考量。我刚开始接触C++智能指针时,也曾纠结于何时用哪种。shared_ptr的自动管理魅力十足,但在循环引用这里,它就像一个美丽的陷阱。
首先是原始指针(raw pointer)。它最直接,也最危险。当主体存储观察者的原始指针时,一旦观察者对象被析构,主体持有的那个地址就成了“悬挂的幽灵”。任何试图通过这个指针进行的操作都可能导致程序崩溃,或者更糟糕的是,静默地破坏数据,这在调试时简直是噩梦。想象一下,一个UI组件作为观察者,在用户关闭它后被销毁了,但后台的数据模型还在尝试向它发送更新通知,那场景简直无法想象。

然后是std::shared_ptr。它解决了所有权问题,多个shared_ptr可以共享同一个对象的生命周期。这听起来很棒,但如果观察者和主体之间形成了相互的shared_ptr引用,比如主体持有shared_ptr<Observer>,而观察者又持有shared_ptr<Subject>(常见于观察者需要调用主体方法进行反注册或查询),那么它们就会形成一个循环。在这种情况下,即使外部已经没有其他shared_ptr指向它们,它们的引用计数也永远不会降到零,导致它们永远不会被析构,最终造成内存泄漏。这就像两个固执的人,谁也不肯先放手,结果一起被困住了。
weak_ptr在观察者模式中的具体实现机制是怎样的?weak_ptr在观察者模式中,扮演的是一个“不干涉”的观察者角色。它本身不拥有对象,也不会增加对象的引用计数。它存在的意义,就是为了能够安全地“窥探”一个shared_ptr所管理的对象是否还活着。
其核心机制在于,当一个shared_ptr被创建时,除了管理实际的对象内存,还会有一个共享的控制块(control block)。这个控制块里维护着两个计数器:一个是强引用计数(strong count),由shared_ptr维护;另一个是弱引用计数(weak count),由weak_ptr维护。
当主体将一个shared_ptr<Observer>转换为weak_ptr<Observer>并存储时,它只增加了弱引用计数。这意味着,即使所有的shared_ptr都释放了对观察者的引用,导致强引用计数归零,观察者对象被销毁,这个weak_ptr仍然可以存在。
关键在于weak_ptr::lock()方法。当主体需要通知观察者时,它会遍历存储的weak_ptr列表,对每一个weak_ptr调用lock()。
lock()成功返回一个有效的shared_ptr(即强引用计数大于0),那么说明被观察的观察者对象仍然存活,主体就可以安全地通过这个shared_ptr调用观察者的更新方法。lock()返回一个空的shared_ptr,则表明被观察的观察者对象已经不存在了(它已经被销毁,因为所有shared_ptr都已释放),此时主体就知道这个weak_ptr已经失效,可以将其从观察者列表中移除。这个lock()方法简直是神来之笔,它优雅地解决了“对象还在不在”这个世纪难题。
概念性代码示例如下:
#include <iostream>
#include <vector>
#include <memory>
#include <algorithm> // For std::remove_if
// 观察者接口
class IObserver {
public:
virtual ~IObserver() = default;
virtual void update() = 0;
};
// 具体观察者
class ConcreteObserver : public IObserver, public std::enable_shared_from_this<ConcreteObserver> {
private:
int id_;
public:
ConcreteObserver(int id) : id_(id) {
std::cout << "Observer " << id_ << " created." << std::endl;
}
~ConcreteObserver() {
std::cout << "Observer " << id_ << " destroyed." << std::endl;
}
void update() override {
std::cout << "Observer " << id_ << " received update." << std::endl;
}
};
// 主体
class Subject {
private:
std::vector<std::weak_ptr<IObserver>> observers_; // 存储weak_ptr
public:
void addObserver(std::shared_ptr<IObserver> observer) {
observers_.push_back(observer);
std::cout << "Subject added an observer." << std::endl;
}
void notify() {
std::cout << "Subject notifying observers..." << std::endl;
// 使用临时vector来避免在迭代时修改原vector导致迭代器失效
std::vector<std::weak_ptr<IObserver>> active_observers;
for (auto& wptr : observers_) {
if (auto sptr = wptr.lock()) { // 尝试提升为shared_ptr
sptr->update(); // 如果成功,说明观察者还在,进行通知
active_observers.push_back(wptr); // 保留活跃的观察者
}
// else: wptr.lock()返回空,说明观察者已销毁,自动从列表中移除
}
observers_ = active_observers; // 更新观察者列表
std::cout << "Notification complete. Active observers count: " << observers_.size() << std::endl;
}
};
// 实际使用
// int main() {
// Subject s;
// {
// auto obs1 = std::make_shared<ConcreteObserver>(1);
// s.addObserver(obs1);
// auto obs2 = std::make_shared<ConcreteObserver>(2);
// s.addObserver(obs2);
//
// s.notify(); // obs1, obs2 都会收到通知
// } // obs1, obs2 在这里超出作用域,被销毁
//
// std::cout << "--- After observers destroyed ---" << std::endl;
// s.notify(); // 此时,s会发现obs1和obs2的weak_ptr已失效,并清理列表
// return 0;
// }weak_ptr解决监听器悬挂问题的最佳实践和注意事项有哪些?我觉得,真正的工程实践,往往不是找到一个完美的银弹,而是权衡利弊,选择最适合当前场景的方案。weak_ptr在这里,就是那个“几乎”完美的答案,但它也有自己的使用之道。
何时使用weak_ptr? 只要你发现存在一个对象需要“知道”另一个对象,但又“不拥有”它,并且这两个对象的生命周期可能独立,或者存在潜在的循环引用风险时,weak_ptr就应该被考虑。在观察者模式中,主体对观察者就是这种关系。观察者通常由其他模块管理其生命周期,主体不应该因为持有观察者的引用而阻止其销毁。
观察者列表的清理: 虽然weak_ptr的lock()方法能判断对象是否存活,但失效的weak_ptr本身并不会自动从容器中移除。因此,在每次通知或周期性地,主体都需要遍历其观察者列表,移除那些已失效的weak_ptr。我上面给出的代码示例,就是在notify方法内部顺便做了清理,这是一个很常见的做法,效率也高。
线程安全性: 如果你的观察者模式涉及多线程,比如一个线程添加观察者,另一个线程通知观察者,或者多个线程同时通知,那么访问observers_列表就需要同步机制(例如std::mutex)。weak_ptr::lock()本身是原子操作,但对std::vector的增删改查则不是。
性能考量: 相比于原始指针的直接解引用,weak_ptr::lock()操作会引入轻微的开销,因为它需要访问控制块并原子地增加强引用计数。但在绝大多数应用场景中,这种开销是微不足道的,安全性带来的收益远超这一点点性能损耗。不要为了极小的性能提升而牺牲稳定性。
显式反注册: 尽管weak_ptr能自动处理已销毁的观察者,但提供一个显式的removeObserver方法仍然是好的实践。这允许观察者在不再需要接收通知时主动解除注册,而不是等到被销毁后才被动地从列表中清理。比如,一个对话框关闭前,它可能希望立即停止接收所有通知,而不是等到下次通知时才被发现已失效。
enable_shared_from_this: 如果你的观察者需要在其自己的成员函数中获取自身的shared_ptr(例如,为了将自己添加到主体中,或者在回调中传递自己的shared_ptr),那么这个观察者类必须继承自std::enable_shared_from_this<T>。这允许对象通过shared_from_this()方法安全地获取自身的shared_ptr,避免了从this指针直接构造shared_ptr的风险。
总而言之,weak_ptr是C++智能指针家族中一个非常实用的成员,尤其是在处理复杂的对象关系和生命周期管理时。它在观察者模式中的应用,完美地体现了其“非拥有但可观察”的特性,让我们的代码更加健壮和安全。
以上就是智能指针在观察者模式中的使用 weak_ptr解决监听器悬挂问题的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号