我最近一直在阅读一些C++书(Sutters,Meyers),这促使我开始更有效地使用智能指针(以及一般的对象破坏)。但是现在我不确定如何解决我所拥有的。具体来说,我现在有一个 IntroScene 类,它继承自 Scene 和 InputListener。
场景并不真正相关,但 InputListener 在构造时订阅了一个 InputManager,并在销毁时再次取消订阅。
class IntroScene : public sfg::Scene, public sfg::InputListener {
/*structors, inherited methods*/
virtual bool OnEvent(sf::Event&) override; //inputlistener
}
但是现在,如果输入管理器将事件发送到场景,并且场景决定自行替换因此,我有一个函数在不再存在的对象上运行。
bool IntroScene::OnEvent(sf::Event& a_Event) {
if (a_Event.type == sf::Event::MouseButtonPressed) {
sfg::Game::Get()->SceneMgr()->Replace(ScenePtr(new IntroScene()));
} //here the returned smartpointer kills the scene/listener
}
附带问题:这重要吗?我用谷歌搜索了一下,但没有找到明确的是或否。我知道 100%销毁已销毁的对象后,不会对其调用任何方法。如果有必要,我可以将 Replace() 返回值存储到 OnEvent() 方法的末尾。
真正的问题是输入侦听器
InputListener::InputListener() {
Game::Get()->InputMgr()->Subscribe(this);
}
InputListener::~InputListener() {
if (m_Manager) m_Manager->Unsubscribe(this);
}
因为它是在 OnEvent() 期间调用的,它在 HandleEvents() 期间由 InputManager 调用
void InputManager::HandleEvents(EventQueue& a_Events) const {
while (!a_Events.empty()) {
sf::Event& e = a_Events.front();
for (auto& listener : m_Listeners) {
if (listener->OnEvent(e)) //swallow event
break;
}
a_Events.pop();
}
void InputManager::Subscribe(InputListener* a_Listener) {
m_Listeners.insert(a_Listener);
a_Listener->m_Manager = this;
}
void InputManager::Unsubscribe(InputListener* a_Listener) {
m_Listeners.erase(a_Listener);
a_Listener->m_Manager = nullptr;
}
因此,当创建新的 Scene+Listener 并销毁旧 Scene+ Listener 时,列表m_Listeners会在循环过程中被修改。所以事情坏了。我已经考虑过在启动和停止循环时设置一个标志,并将在单独的列表中设置时发生的(取消)订阅存储,并在之后处理。但感觉有点笨拙。
那么,我怎样才能正确地重新设计它以防止这种情况发生呢?提前谢谢。
编辑,解决方案:我最终选择了循环标志和延迟条目列表(下面有 inetknight 的答案)仅适用于订阅,因为以后可以安全地完成。
取消订阅必须立即处理,因此我存储一个(指针可变布尔值)对(可变,因为集合仅返回const_iterator,因此无需存储原始指针)。发生这种情况时,我将布尔值设置为 false,并在事件循环中检查它(请参阅下面的 dave 评论)。不确定这是最干净的解决方案,但它就像一个魅力。非常感谢大家
附带问题:这重要吗?我用谷歌搜索了一下,但没有找到明确的是或否。我确实知道 100% 在被摧毁的对象被销毁后没有调用任何方法。如果有必要,我可以将 Replace() 返回值存储到 OnEvent() 方法的末尾。
如果您知道 100% 没有在销毁的对象上调用任何方法,并且没有访问其成员变量,那么它是安全的。它是否有意取决于您。
您可以有另一个已请求取消/订阅的对象列表。然后,在您告诉事件列表中的每个人之后,您将处理取消/订阅请求列表,然后再继续下一个事件。
/* this should be a member of InputManager however you did not provide a class definition */
typedef std::pair<InputListener *, bool> SubscriptionRequest;
bool handleEventsActive = false;
std::vector<SubscriptionRequest> deferredSubscriptionRequests;
void InputManager::HandleEvents(EventQueue& a_Events) const {
// process events
handleEventsActive = true;
while (!a_Events.empty()) {
sf::Event& e = a_Events.front();
for (auto& listener : m_Listeners)
{
//swallow event
if (listener->OnEvent(e)) {
break;
}
}
a_Events.pop();
// process deferred subscription requests occurred during event
while ( not deferredSubscriptionRequests.empty() ) {
SubscriptionRequest request = deferredSubscriptionRequests.back();
deferredSubscriptionRequests.pop_back();
DoSubscriptionRequest(request);
}
}
handleEventsActive = false;
}
void InputManager::DoSubscriptionRequest(SubscriptionRequest &request) {
if ( request.second ) {
m_Listeners.insert(request.first);
request.first->m_Manager = this;
} else {
m_Listeners.erase(request.first);
request.first->m_Manager = nullptr;
}
}
void InputManager::Subscribe(InputListener* a_Listener)
{
SubscriptionRequest request{a_Listener, true};
if ( handleEventsActive ) {
deferredSubscriptionRequests.push_back(request);
} else {
DoSubscriptionRequest(request);
}
}
void InputManager::Unsubscribe(InputListener* a_Listener)
{
SubscriptionRequest request{a_Listener, false};
if ( handleEventsActive ) {
deferredSubscriptionRequests.push_back(request);
} else {
DoSubscriptionRequest(request);
}
}