1

我有一个 UIManager 管理从单个 UI 类继承的一系列类。目前,它的工作原理是这样的,其中各个 UI 被延迟初始化并静态存储:

class UIManager
{

public:
   UIManager();            // Constructor
   virtual ~UIManager();   // Destructor


   template <typename T>
   T *getUI()
   {
      static T ui();       // Constructs T, stores result in ui when
                           // getUI<T>() is first called
      return &ui;    
   }
}

调用:

getUI<NameEntryUI>()->activate();

或者

getUI<MenuUI>()->render();

我正在考虑一项设计更改,允许我拥有多个玩家,因此多个游戏窗口,因此多个 UIManager。我希望在删除 UIManager 时清理所有构造的 ui 对象(目前,因为 ui 对象是静态的,它们会一直存在直到程序退出)。

当 UIManager 被杀死时,如何重写上述内容以删除 ui 对象?

=======================================

这是我实施的解决方案。早期的结果是它运作良好。

基本上,我从 Potatoswatter 提出的想法开始,我喜欢它,因为它类似于我开始的方法,然后因为我不知道 typeid(T) 而中止。我将代码反向移植为仅使用 C++98 功能。整个事情的关键是 typeid(T),它可以让你以一致的方式将实例化的接口映射到它们的类型。

class UIManager
{
   typedef map<const char *, UserInterface *> UiMapType;
   typedef UiMapType::iterator UiIterator;

   map<const char *, UserInterface *> mUis;

public:
   UIManager();            // Constructor
   virtual ~UIManager()    // Destructor
   {
      // Clear out mUis
      for(UiIterator it = mUis.begin(); it != mUis.end(); it++) 
         delete it->second;

      mUis.clear();
   }

   template <typename T>
   T *getUI()
   {
      static const char *type = typeid(T).name();

      T *ui = static_cast<T *>(mUis[type]);
      if(!ui)
         ui = new T();

      mUis[type] = ui;

      return ui;
   }
}
4

2 回答 2

2

目前,您只能为每种类型的一个 UI 元素分配存储空间。从根本上说,要保持这一原则却拥有任意数量的窗口是不可能的。

快速而肮脏的解决方案是为窗口编号添加一个模板参数。如果它是一个游戏,并且您只有有限数量的玩家,您可以为一些预定数量的窗口提供静态存储。

template <typename T, int N>
T *getUI()

但是,将 UI 标识与类型系统绑定的方法存在根本缺陷,我建议使用更传统的方法,使用多态性和容器。


按类型识别对象但动态存储它们的一种方法可能看起来像

class UIManager {
    std::map< std::type_index, std::unique_ptr< UIBase > > elements;

    template< typename T >
    T & GetUI() { // Return reference because null is not an option.
        auto & p = elements[ typeid( T ) ];
        if ( ! p ) p.reset( new T );
        return dynamic_cast< T & >( * p );
    }
}

请注意,这需要UIBase有一个虚拟析构函数,否则当您退出时对象将无法正确终止。

于 2013-09-10T23:06:35.530 回答
1

由于每种类型显然需要多个对象,因此我们只需将对象存储在std::map<UIManager const*, T>. 要提取特定的托管对象,请在映射中查找相应类型。棘手的一点是稍后摆脱使用函数对象列表处理的对象:

class UIManager
{
    std::vector<std::function<void()>> d_cleaners;
    UIManager(UIManager const&) = delete;
    void operator=(UIManager const&) = delete;
public:
    UIManager();
    ~UIManager();

    template <typename T>
    T *getUI() {
        static std::map<UIManager const*, T> uis;
        typename std::map<UIManager const*, T>::iterator it = uis.find(this);
        if (it == uis.end()) {
            it = uis.insert(std::make_pair(this, T())).first;
            this->d_cleaner.push_back([it, &uis](){ uis.erase(it); });
        }
        return &(it->second);   
    }
};

相应的函数存储将 的地址(即 )getUI()映射到相应对象的映射。如果没有这样的映射,则插入一个新的映射。另外,为了确保对象被清理,一个清理函数被注册到,简单地在从对应映射中获得的迭代器中。代码未经测试,但类似的东西应该可以工作。UIManagerthisthiserase()

于 2013-09-10T23:18:46.860 回答