开发者

avoiding enums as interface identifiers c++ OOP

开发者 https://www.devze.com 2022-12-24 07:26 出处:网络
I\'m working on a plugin framework using dynamic loaded shared libraries which is based on Eclipse\'s (and probally other\'s) extension-point model. All plugins share similar properties (name, id, ver

I'm working on a plugin framework using dynamic loaded shared libraries which is based on Eclipse's (and probally other's) extension-point model. All plugins share similar properties (name, id, version etc) and each plugin could in theory satisfy any extension-point. The actual plugin (ie Dll) handling is managed by another library, all I am doing really is managing collections of interfaces for the application.

I started by using an enum PluginType to distinguish the different interfaces, but I have quickly realised that using template functions made the code far cleaner and would leave the grunt work up to the compiler, rather than forcing me to use lots of switch {...} statements.

The only issue is where I need to specify like functionality for class members - most obvious example is the default plugin which provides a particular interface. A Settings class handles all settings, including the default plugin for an interface.

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

How do I store the default ISkin in a container without resorting to some other means of identifying the interface?

As I mentioned above, I currently use a std::map<PluginType, IPlugin> Settings::defaults member to achieve this (where IPlugin is an abstract base class which all plugins derive from. I can then dynamic_cast to the desired interface when required, but this really smells of bad design to me and introduces more harm than good I think.

would welcome any tips

edit: here's an example of the current use of default plugins

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();
}

I would much rather call the getdefault as the following, with automatic casting to the derived class. However I need to specialize for every class type.

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


You could use a sequence container of boost::variant instead (untested illustrative code):

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

Then:

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);
}


What is the problem of the enum ? The lack of extensibility.

How to have extensibility and yet retain identification ? You need a full blown object, preferably with a specific type.

Basically you can get away with:

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; }
};

Now, as for using, it would get:

// skin.h

class ISkin;

struct SkinId: PluginId<ISkin> {}; // Types can be forward declared
                                   // Typedef cannot

class ISkin: public IPlugin { /**/ };

And now you can get use:

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);
};

The template version is implemented in term of the non-template one and performs the downcast automatically. There is no way the pointer might be the wrong type because the compiler does the type checking, thus you get away with a static_cast :)

I know downcasting all over the place is kind of ugly, but here you just down_cast in one method GetDefault and it's type checked at compile time.

Even easier (let's generate the keys on the fly):

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;
};

However it's less safe than the first alternative, notably you can put anything there as long as it inherits from IPlugin, so you can put MySkin for example, and you won't be able to retrieve it through ISkin because typeid(T).name() will resolve to a different name.


Downcasting may be avoided by using the Visitor-Pattern, but this may require substantial refactoring of you architecture. This way, you also do not have to handle the plugins differently. Creating instances of plugins can be done using a Factory. Hope that gives you some starting point. If you wish more details, you have to provide more information on you architecture.


I'm pretty sure you can do something like this.

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();
}        

Line (1) is the part I think I've seen done before but don't know how to do myself. Also note that the current implementation returns a null pointer if you pass a type the Settings class hasn't seen yet. You'll have to account for that in some way.

0

精彩评论

暂无评论...
验证码 换一张
取 消