如何在创建范围结束时自动从单一实例管理器类中删除shared_ptr

How to automatically remove shared_ptr from Singleton Manager class at the end of scope of its creation

本文关键字:管理器 实例 删除 ptr shared 单一 创建 范围 结束      更新时间:2023-10-16

我有一个包含Button对象的向量的单例ButtonManagerButtonManager主体之所以存在,是因为它在调用HandleEvents时将事件分发给所有Button观察者。出于其他一些原因,它需要成为单例。

我的

问题是我想消除我的Button必须手动从我的ButtonManager中删除自己的需要。理想情况下,在我Create Button的作用域结束时,ButtonManager 也会失去对它的引用。

class ButtonManager : public EventHandler
{
public:
   static ButtonManager & Instance();
public:
   // push_back a Button to the vector and return it
   std::shared_ptr<Button> Create(const Rect & _rect, const std::string _text, const Colour & _fg, const Colour & _bg);
   // Erase the button pointer from the vector
   void Remove(const std::shared_ptr<Button> & _button);
   // EventHandler
   void HandleEvents( const Event & _event );
   ...
private:
   std::vector<std::shared_ptr<Button>> buttons;
};
class Button : public EventHandler
{
public:
   Button(const Rect & _rect, const std::string & _text, const Colour & _fg, const Colour & _bg);
   ...
   // EventHandler
   void HandleEvents( const Event & _event );
};

当前方案:

{
   std::shared_ptr<Button> ok_button_ptr = UI::ButtonManager::Instance().Create(UI::Rect(5, 5, 5, 1), string("OK"), UI::Colour::White, UI::Colour::DodgerBlue);
   // events are processed and distributed to the buttonmanager 
   // which then distributes to the buttons
   UI::ButtonManager::Instance().Remove(ok_button_ptr);
}

理想场景:

{
   std::shared_ptr<Button> ok_button_ptr = UI::ButtonManager::Instance().Create(UI::Rect(5, 5, 5, 1), string("OK"), UI::Colour::White, UI::Colour::DodgerBlue);
   // events are processed and distributed to the buttonmanager 
   // which then distributes to the buttons
   // ok_button_ptr loses its reference here and the ButtonManager erase's the shared_ptr as well if it holds the last reference to the Button
}

在范围退出时释放资源可以使用 RAII 完成。创建一个 ButtonHolder 类,该类将shared_ptr保存为成员并在其析构函数中调用 Remove

class ButtonHolder
{
public:
    ButtonHolder(std::shared_ptr<Button> b): theButton(std::move(b)) {}
    ~ButtonHolder() {
        UI::ButtonManager::Instance().Remove(theButton);
    }
    // could give it shared_ptr interface, e.g.
    Button& operator*() const;
    Button& operator->() const;
    // etc
private:
    std::shared_ptr<Button> theButton;
};
{
   // get button from singleton. Ref count increases by one. Let's say it is now 2.
   ButtonHolder ok_button_ptr( UI::ButtonManager::Instance().Create(...) );
   // events are processed and distributed to the buttonmanager 
   // which then distributes to the buttons
   // ButtonHolder::~ButtonHolder is called which removes the button from the 
   // singleton (ref count = 1) and then deletes its own shared_ptr member (ref count = 0)
   // to delete the button object completely
}

这个问题是失效的侦听器问题:http://en.wikipedia.org/wiki/Lapsed_listener_problem

解决方案是使用 weak_ptr vector而不是shared_ptr,因为weak_ptr是非拥有智能指针。

  class ButtonManager : public IUIObservable
   {
   public:
      static ButtonManager & Instance();
      void Subscribe(const std::shared_ptr<IUIObservable> & _o);
      void HandleEvents( const sf::Event & _event );
      ...
   private:
      std::vector<std::weak_ptr<IUIObservable>> observers;
   };

   void ButtonManager::Subscribe(const std::shared_ptr<IUIObservable> & _o)
   {
      observers.push_back(_o);
   }
   void ButtonManager::HandleEvents( const sf::Event & _event )
   {
        // Remove any dead observers. These are the ones that have expired().
        this->observers.erase(std::remove_if(this->observers.begin(), this->observers.end(),
            [](const std::weak_ptr<IUIObservable>& _element)
        {
            return _element.expired();
        }), this->observers.end());
      // go through all the elements and handle events
      for (auto& observer_weak_ptr : this->observers)
      {
         auto observer_ptr = observer_weak_ptr.lock();
         if (observer_ptr)
         {
            observer_ptr->HandleEvents(_event);
         }
      }
   }

使用示例:

  auto ok_button = std::make_shared<Button>(UI::Rect(5, 5, 5, 1), string("OK"), UI::Colour::White, UI::Colour::DodgerBlue);
  UI::ButtonManager::Instance().Subscribe(ok_button);
  {
     auto tmp_button = std::make_shared<Button>(UI::Rect(6, 5, 5, 1), string("Tmp"), UI::Colour::White, UI::Colour::DodgerBlue);
     UI::ButtonManager::Instance().Subscribe(tmp_button);
     // tmp_button will be expired on the next process events
  }
  // Process Events