views:

113

answers:

2

I'm working on a plugin framework, which supports multiple variants of a base plugin class CPlugin : IPlugin. I am using a boost::shared_ptr<IPlugin> for all reference to the plugins, except when a subsystem needs the plugin type's specific interface. I also need the ability to clone a plugin into another seprate object. This must return a PluginPtr. This is why CPlugin is a template rather than a straight class. CPlugin::Clone() is where the template paramter is used. The following are the class definitions I am using:

IPlugin.h

#include "PluginMgr.h"
class IPlugin;
typedef boost::shared_ptr<IPlugin> PluginPtr;

class IPlugin
{
public:
  virtual PluginPtr Clone() =0;
  virtual TYPE Type() const =0;
  virtual CStdString Uuid() const =0;
  virtual CStdString Parent() const =0;
  virtual CStdString Name() const =0;
  virtual bool Disabled() const =0;

private:
  friend class CPluginMgr;
  virtual void Enable() =0;
  virtual void Disable() =0;
};

CPlugin.h

#include "IPlugin.h"
template<typename Derived>
class CPlugin : public IPlugin
{
public:
  CPlugin(const PluginProps &props);
  CPlugin(const CPlugin&);
  virtual ~CPlugin();
  PluginPtr Clone();

  TYPE Type() const { return m_type; }
  CStdString Uuid() const { return m_uuid; }
  CStdString Parent() const { return m_guid_parent; }
  CStdString Name() const { return m_strName; }
  bool Disabled() const { return m_disabled; }

private:
  void Enable() { m_disabled = false; }
  void Disable() { m_disabled = true; }

  TYPE        m_type;
  CStdString  m_uuid;
  CStdString  m_uuid_parent;
  bool        m_disabled;
};

template<typename Derived>
PluginPtr CPlugin<Derived>::Clone()
{
  PluginPtr plugin(new Derived(dynamic_cast<Derived&>(*this)));
  return plugin;
}

An example concrete class CAudioDSP.h

#include "Plugin.h"
class CAudioDSP : CPlugin<CAudioDSP>
{
  CAudioDSP(const PluginProps &props);
  bool DoSomethingTypeSpecific();
  <..snip..>
};

My problem (finally) is that CPluginMgr needs to update m_disabled of the concrete class, however as it is passed a PluginPtr it has no way to determine the type and behave differently according to the template paramater. I can't see how to avoid declaring ::Enable() and ::Disable() as private members of IPlugin instead but this instantly means that every section of the application now needs to know about the CPluginMgr class, as it is declared as a friend in the header. Circular dependancy hell ensues. I see another option, declare the Enable/Disable functions as private members of CPlugin and use boost::dynamic_pointer_cast<CVariantName> instead.

void CPluginMgr::EnablePlugin(PluginPtr plugin)
{
  if(plugin->Type == PLUGIN_DSPAUDIO)
  {
    boost::shared_ptr<CAudioDSP> dsp = boost::dynamic_pointer_cast<CAudioDSP>(plugin);
    dsp->Enable();
  }
}

This however leads to lots of duplicate code with many multiple variants of the base CPlugin template. If anyone has a better suggestion please share it!

+2  A: 

You can easily write :

class CPluginMgr;

class IPlugIn ..
{
    friend CPluginMgr;
    ...
};

Only a predefinition is needed for friend.

Christopher
I can't believe it's so simple, yet it is :)Thanks Christopher!
AlasdairC
Don't forget to accept Christopher solution ...
neuro
err I did, sorry about that
AlasdairC
A: 

I think your get in trouble trying to return a shared_ptr in clone method. Why don't you make use of covariant return types? What you are doing is a common idiom called Virtual Constructor.

class IPlugin
{
public:
    virtual IPlugin* clone() = 0;
    // ...
}

class CPluginMgr;

class CPlugin : public IPlugin
{
public:
    virtual CPlugin* clone() = 0;
    friend CPluginMgr; // as @Christopher pointed out
    void Enable(bool enable) { m_disabled = !enable; }
    // ...
}

class CAudioDSP : public CPlugin 
{
public:
    virtual CAudioDSP* clone();
    // ...
}

CAudioDSP* CAudioDSP::clone()
{
    return new CAudioDSP(*this); // assume copy constructors are properly implemented
}

Returning a shared_ptr may lead you to make errors (as early destruction of temparary objects) and I think is not usually a good idea.

fnieto