【问题标题】:avoiding enums as interface identifiers c++ OOP避免枚举作为接口标识符 c++ OOP
【发布时间】:2010-03-25 13:36:09
【问题描述】:

我正在使用基于 Eclipse(可能还有其他)扩展点模型的动态加载共享库开发插件框架。所有插件共享相似的属性(名称、id、版本等),理论上每个插件都可以满足任何扩展点。实际的插件(即 Dll)处理由另一个库管理,我所做的只是管理应用程序的接口集合。

我一开始使用enum PluginType 来区分不同的接口,但我很快意识到使用模板函数可以使代码更简洁,并将繁琐的工作留给编译器,而不是强迫我使用大量switch {...} 声明。

唯一的问题是我需要为类成员指定类似的功能 - 最明显的例子是提供特定接口的默认插件。 Settings 类处理所有设置,包括接口的默认插件。

Skin newSkin = settings.GetDefault<ISkin>();

如何将默认的ISkin 存储在容器中,而不使用其他识别接口的方法?

正如我上面提到的,我目前使用std::map<PluginType, IPlugin> Settings::defaults 成员来实现这一点(其中IPlugin 是所有插件派生自的抽象基类。然后我可以在需要时将dynamic_cast 到所需的接口,但这对我来说真的闻起来很糟糕,而且我认为弊大于利。

欢迎任何提示

编辑:这是当前使用默认插件的示例

typedef boost::shared_ptr<ISkin> Skin;
typedef boost::shared_ptr<IPlugin> Plugin;
enum PluginType
{
  skin,
  ...,
  ...
}

class Settings
{
public:
  void SetDefault(const PluginType type, boost::shared_ptr<IPlugin> plugin) {
    m_default[type] = plugin;
  }
  boost::shared_ptr<IPlugin> GetDefault(const PluginType type) {
    return m_default[type];
  }
private:
  std::map<PluginType, boost::shared_ptr<IPlugin> m_default;
};

SkinManager::Initialize()
{
  Plugin thedefault = g_settings.GetDefault(skinplugin);
  Skin defaultskin = boost::dynamic_pointer_cast<ISkin>(theskin);
  defaultskin->Initialize();
}

我更愿意将 getdefault 调用如下,并自动转换为派生类。但是,我需要专门针对每种课程类型。

template<>
Skin Settings::GetDefault<ISkin>()
{
  return boost::dynamic_pointer_cast<ISkin>(m_default(skin));
}

【问题讨论】:

  • 您能否发布一些示例代码,说明如何使用默认值?我很难想象你在说什么。
  • 如果IPlugin 是一个抽象类,您需要在地图中存储指针(无论如何您都需要这样做,否则会遇到切片问题)。
  • 嗨 Kristo,添加了更多示例
  • 抱歉 James 我应该指定我正在存储 shared_ptr(IPlugin)。我正在使用 boost::shared_ptrs 来避免挂起指向已卸载 Dll 的指针。

标签: c++ oop templates plugins


【解决方案1】:

您可以改用 boost::variant 的序列容器(未经测试的说明性代码):

tyepdef boost::variant<
boost::shared_ptr<ISkin>,
boost::shared_ptr<IPluginType2>,
boost::shared_ptr<IPluginType3>,
etc...> default_t;
std::deque<default_t> defaults;

然后:

template <class T>
boost::shared_ptr<T> GetDefault() {
    for(std::deque<default_t>::iterator it = defaults.begin(), endIt = defaults.end();
        it != endIt;
        ++it) {
       boost::shared_ptr<T>* result = boost::get< boost::shared_ptr<T> >(*it);
       if( result ) {
           return *result;
       }
    }
    return boost::shared_ptr<T>(0);
}

【讨论】:

    【解决方案2】:

    枚举的问题是什么?缺乏可扩展性。

    如何在具有可扩展性的同时保留标识?您需要一个完整的对象,最好是具有特定类型的对象。

    基本上你可以逃脱:

    class IPluginId
    {
    public:
      virtual IPluginId* clone() const = 0;
      virtual ~IPluginId();
    
      bool operator<(const IPluginId& rhs) const { return mId < rhs.mId; }
      bool operator==(const IPluginId& rhs) const { return mId == rhs.mId; }
    
    protected:
      static size_t IdCount = 0;
      IPluginId(size_t id): mId(id) {}
    private:
      size_t mId;
    };
    
    template <class Plugin>
    class PluginId
    {
    public:
      PluginId(): IPluginId(GetId()) {}
      IPluginId* clone() const { return new PluginId(*this); }
    private:
      static size_t GetId() { static size_t MId = ++IdCount; return MId; }
    };
    

    现在,至于使用,它会得到:

    // skin.h
    
    class ISkin;
    
    struct SkinId: PluginId<ISkin> {}; // Types can be forward declared
                                       // Typedef cannot
    
    class ISkin: public IPlugin { /**/ };
    

    现在你可以使用了:

    class Settings
    {
    public:
      template <class Plugin>
      void SetDefault(boost::shared_ptr<Plugin> p);
    
      template <class Plugin>
      boost::shared_ptr<Plugin> GetDefault(const PluginId<Plugin>& id);
    
    private:
      boost::shared_ptr<IPlugin> GetDefault(const IPluginId& id);
    };
    

    模板版本是根据非模板版本实现的,并自动执行向下转换。指针不可能是错误的类型,因为编译器会进行类型检查,因此您可以使用static_cast :)

    我知道到处向下转换有点难看,但在这里你只需在一个方法GetDefault 中进行向下转换,并在编译时检查类型。

    更简单(让我们动态生成密钥):

    class Settings
    {
    public:
      template <class Plugin>
      void SetDefault(const boost::shared_ptr<Plugin>& p)
      {
        mPlugins[typeid(Plugin).name()] = p;
      }
    
      template <class Plugin>
      boost::shared_ptr<Plugin> GetDefault() const
      {
        plugins_type::const_iterator it = mPlugins.find(typeid(Plugin).name());
        if (it == mPlugins.end()) return boost::shared_ptr<Plugin>();
    
        return shared_static_cast<Plugin>(it->second);
      }
    
    private:
      typedef std::map<std::string, std::shared_ptr<IPlugin> > plugins_type;
      plugins_type mPlugins;
    };
    

    但是它不如第一种安全,特别是你可以放任何东西,只要它继承自IPlugin,所以你可以放MySkin,例如,你将无法通过@检索它987654329@ 因为typeid(T).name() 将解析为不同的名称。

    【讨论】:

    • 嗨,马蒂厄,感谢您的回答。从后者开始,我真的很喜欢你使用 type_info.name 属性的想法,它一次性完全解决了我的问题。我今天实际上正在查看 typeid 之类的东西,但未能实现信念的飞跃,以意识到我可以以多种不同的方式将其用作属性。但是,我并没有完全遵循您的第一个建议,如果您仍然热衷 [:)],您能否提供两种 GetDefault 方法的实现?如果可能的话,我肯定更喜欢将其作为 static_cast 来做
    • 很高兴能帮上忙 :) 如果您想为一个共同的 id 值设置多个插件类型,您也许可以使用 multimap
    • 我不知道这是否可能,但在某些情况下,我想从 ISkin 获取 SkinId 类型(例如)。有什么办法可以避免将IPluginId 作为参数传递给GetDefault 方法?即类型是否通过返回类型声明的T 参数解析?目前我必须像下面这样传递 id boost::shared_ptr&lt;ISkin&gt; skin = settings.GetDefault(SkinId());
    • 好吧,我发现这是不可能的,类似于不允许的返回类型重载。所以在这个建议和上面的 boost::variant 中,我总是需要传递一个模板参数
    【解决方案3】:

    使用Visitor-Pattern 可以避免向下转换,但这可能需要对您的架构进行大量重构。这样,您也不必以不同的方式处理插件。可以使用Factory 创建插件实例。希望这能给你一些起点。如果您希望了解更多详细信息,则必须提供有关您的架构的更多信息。

    【讨论】:

    • 您好,看起来访问者模式可能是一个更好的选择,因为理论上每个插件都可以提供任何接口。然而,我有点伤心,因为要实现这一点需要进行重大的重构。
    【解决方案4】:

    我很确定你可以做这样的事情。

    class Settings
    {
        public:
            // ...
            template <class T>
            boost::shared_ptr<T> GetDefault()
            {
                // do something to convert T to an object (1)
                return m_default[T_as_an_obj];
            }
            // ....
    };
    
    SkinManager::Initialize()
    {
        boost::shared_ptr<ISkin> defaultskin = g_settings.GetDefault<ISkin>();
        defaultskin->Initialize();
    }        
    

    第 (1) 行是我认为我以前见过但不知道该怎么做的部分。另请注意,如果您传递设置类尚未看到的类型,则当前实现将返回空指针。您必须以某种方式考虑到这一点。

    【讨论】:

    • 嗨克里斯托,是的,我总是在通话后检查指针以确保安全。就像你说的那样,第 1 行是最难的部分,无需使用 switch 语句、typeid(T) 等。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-10-26
    • 2015-08-06
    • 1970-01-01
    • 1970-01-01
    • 2023-03-05
    • 2021-07-12
    相关资源
    最近更新 更多