views:

128

answers:

4

Hi

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

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.

Space_C0wb0y
Hi,It looks like the visitor-pattern might have been a far better choice given that each plugin could in theory provide any of the interfaces. I'm a bit torn however, as major refactoring would be required to achieve this.
AlasdairC
+1  A: 

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);
}
Joe Gauterin
That looks like a good solution, thanks for the suggestion
AlasdairC
A: 

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.

Kristo
Hi Kristo, yeah I always check the pointer after the call to be safe. Line 1 like you say is the hard part, without resorting to switch statements, typeid(T) and the like.
AlasdairC
A: 

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.

Matthieu M.
Hi Matthieu,thanks for the answer. Starting with the latter, I really like your idea of using the `type_info.name` property, it completely solves my problem in one go. I was actually looking at typeid and the like today and failed to make the leap of faith to realize I could use this as property in many different ways.However I'm not completely following your first suggestion, if you are still keen [:)] could you provide the implementation for both GetDefault methods?I'd definitely prefer to do this as a static_cast if possible
AlasdairC
Actually, that's not needed I have played around with this and it's working very well, much better than an enum and not using any magic such as the typeid(T).name() call.I have another usecase which is similar.`template<class Plugin>``std::vector<boost::shared_pointer<T> > GetPlugins(const PluginId<Plugin>`I'm currently storing these plugins in a `std::map<IPluginId, boost::shared_ptr<IPlugin> >` container.
AlasdairC
Glad to be of help :) You could perhaps use a `multimap` if you want to have multiple plugins type for a common id value.
Matthieu M.
I don't know if this is possible, but there's cases where I'd like to get the `SkinId` type from an `ISkin` (for example).Is there any way I can avoid passing the `IPluginId` as a paramater to the `GetDefault` method? ie have the type resolved via the `T` paramater of the return type declaration?At the moment I have to pass the id like the following`boost::shared_ptr<ISkin> skin = settings.GetDefault(SkinId());`
AlasdairC
well I've discovered that's impossible, would be akin to return type overloading which isn't allowed. so in both this suggestion and the boost::variant one above I will always need to pass a template parameter
AlasdairC