【问题标题】:std::enable_shared_from_this with different owner具有不同所有者的 std::enable_shared_from_this
【发布时间】:2014-09-19 20:59:49
【问题描述】:

还有另一个 enable_shared_from_this 问题:基本上,我得到了三件事。

  1. 系统类包含应用程序逻辑,可能是也可能不是事件监听器。
  2. 某种 EventManager,将事件映射到感兴趣系统的 shared_ptrs。
  3. 某种 SystemManager,它持有一个 std::list 的 shared_ptrs 到 System 对象。

现在,每个系统都应该能够向 EventManager 注册它感兴趣的各种事件。此外,如果它们不再对这些事件感兴趣,它们应该能够注销自己的这些事件.

但是,我不确定如何在系统管理器中创建系统并以避免两个所有权组的方式进行注册。

来自 SystemManager:

void SystemManager::AddSystem(GameSystem* system)
{
    // Take ownership of the specified system.
    systems.push_back(std::shared_ptr<GameSystem>(system));
}

来自事件管理器:

typedef std::shared_ptr<IEventListener> EventListenerPtr;

void EventManager::AddListener(EventListenerPtr const & listener, HashedString const & eventType)
{
    // Get the event type entry in the listeners map.
    std::map<unsigned long, std::list<EventListenerPtr>>::iterator iterator = this->listeners.find(eventType.getHash());

    if (iterator != this->listeners.end())
    {
        std::list<EventListenerPtr> & eventListeners = iterator->second;

        // Add listener.
        eventListeners.push_back(listener);
    }
    else
    {
        // Add new entry to listeners map, removed for sake of simplicity of this example.
    }
}

来自某个想要接收事件的任意系统:

void RenderSystem::InitSystem(std::shared_ptr<GameInfrastructure> game)
{
    // BAD: Creates second ownership group.
    this->game->eventManager->AddListener(std::shared_ptr<IEventListener>(this), AppWindowChangedEvent::AppWindowChangedEventType);
}

我已阅读推荐 enable_shared_from_this 的参考资料。但是,由于系统不拥有自己,因此它们无权访问最初创建系统并取得系统所有权的 SystemManager 持有的 shared_ptr。

有什么优雅的方法可以解决这个问题吗?

【问题讨论】:

  • 这就是enable_shared_from_this 的用例,它使您可以访问拥有您的shared_ptr

标签: c++ c++11 boost shared-ptr smart-pointers


【解决方案1】:

您的第一个问题是IEventListener。我猜它是一个简单回调的接口。

不要对简单回调的接口使用继承,对简单回调使用类型擦除。

typedef std::function<void(EventData)> EventListener;
typedef std::weak_ptr<EventListener> wpEventListener;
typedef std::shared_ptr<EventListener> EventToken;

EventToken EventManager::AddListener(EventListener listener, HashedString const & eventType)
{
  // note type changes (3 of them!) -- change the type of this->listeners to match
  // probably HashedString should have a std::hash<HashedString> specialization to make this
  // less painful
  // also should be auto iterator = -- the type of an iterator is not interesting.
  std::unordered_map<unsigned long, std::vector<wpEventListener>>::iterator iterator = this->listeners.find(eventType.getHash());

  if (iterator != this->listeners.end())
  {
    auto & eventListeners = iterator->second;

    EventToken retval = std::make_shared<EventListener>(std::move(listener));
    eventListeners.push_back(retval);
    return retval;
  } else {
    // Add new entry to listeners map, removed for sake of simplicity of this example.
  }
}

现在,当您安装 Listener 时,您有责任保留您的 EventToken,直到您不再想收听它。不想听的时候,EventToken.reset()

EventManager 中,当您迭代收听的人时,执行auto listener = it-&gt;lock(); if (!listener) continue;,此外还执行快速erase-remove-if 以删除已死的weak_ptrs。

现在我们的EventManager 不拥有其听众的生命周期,他们可以(几乎)随时离开。 EventManager 会在下次调用事件时发出通知,并清除已死的事件。

您可以将其改造成使用IEventListener,但即使在这里您也需要弱指针,而不是共享指针。到处使用共享指针会给你带来麻烦,因为你会有大量的循环自支持引用和资源泄漏。

最后,shared_from_this 实际上允许您访问拥有您的shared_ptr。这意味着当shared_ptr被构造时,weak_ptr被存储在shared_from_this中,即使你只有this指针,以后也可以提取。

我通常认为这是一个不好的迹象:您应该很少在随机上下文中将原始指针升级为共享指针。方法的生命周期语义从其接口应该是显而易见的,并且将 T* 内部转换为 shared_ptr&lt;T&gt; 会使生命周期语义完全不清楚。

【讨论】:

  • 嘿 Yakk,谢谢您的详细回答! std::function 对我来说绝对看起来更干净,我只是想把所有东西都重构到这个里面。但是,如果我得到了正确的模式(来自 stackoverflow.com/questions/14814158/… 等其他讨论),事件管理器将需要引用所有类的对象来调用回调?如果这是真的,它不需要存储那些......?还是我错过了什么?
  • @NickPruehs 不,该类在回调中存储对自身的引用。并且该类将shared_ptr 令牌存储到其自身的回调中。当它死亡时,它会杀死事件令牌,从而防止 大多数 在被删除后调用它的情况。 (如果回调导致对象被删除,那是你的错!)即,将[this]( event_data d ){ this-&gt;ProcessEvent(d); } 传递给AddListener 作为你的EventListener
  • 您的解决方案适用于所有不将管理对象作为参数的函数。由于我正在编写一个 Windows Store 应用程序,我只有其中的几个,所以我现在坚持使用界面解决方案,但我没有提到这个要求,所以再次感谢您的回答!不要在任何地方使用 shared_ptrs 的提示使它起作用。
猜你喜欢
  • 2023-01-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-11-27
相关资源
最近更新 更多