tags:

views:

90

answers:

3

So I've got a plugin-based system I'm writing. Users can create a child class of a Plugin class and then it will be loaded at runtime and integrated with the rest of the system. When a Plugin is run from the system, it's run in the context of a group of plugins, which I call a Session.

My problem is that inside the user plugins, two streaming classes called pf_ostream and pf_istream can be used to read/write data to the system. I'd like to bind the plugin instance's session variable to pf_ostream and pf_istream somehow so that when the user instantiates those classes, it's already bound to the session for them (basically I don't want them to see the session internals)

I could just do this with a macro, wrapping a call to the constructor like:

#define MAKE_OSTREAM = pf_ostream_int(this->session)

But I thought there might be a better way. I looked at using a nested class inside Plugin wrapping pf_ostream but it appears nested classes don't get access to the enclosing classes variables in a closure sort of way.

Does anyone know of a neat way to do this?

A: 

You can give the nested classes a reference to their parent class during their construction.

class plugin_base {
protected:
   plugin_base(): _istream(*this) {}
   class istream {
   public:
      istream(plugin_base& p): _plugin(p) {}
      ...
   private:
      plugin_base& _plugin;
   };
   istream _istream;
};

class plugin: public plugin_base {
   ...
};

The constructor of plugin_base will be automatically called by the compiler every time you instantiate a subclass of it.

Will
Can you give an example of how this is done?
gct
Hmmm looking into it, it looks like you'd still need to explicitly pass a pointer to the enclosing class. I'm looking for something that isn't visible through the inner class constructor.
gct
In my system the outer class is a parent for the actual classes that are instantiated as plugins, so the user would have to still pass the plugin reference explicitly.
gct
@gct it gets called automatically; I'll illustrate the code snippet more
Will
A: 

An interface class for things having a Session could be used. Here's some food for thought:

class IContext
{
public:
    virtual ~IContext() {}
    virtual Session* session() = 0;
};


class Plugin : public IContext
{
public:
    Plugin(Session* session) : session_(session) {}
    virtual ~Plugin() {}
    virtual Session* session() {return session_;}

protected:
    Session* session_;
};


class MyPlugin : public Plugin
{
public:
    MyPlugin(Session* session) : Plugin(session) {}
    virtual ~MyPlugin() {}
    void print()
    {
        pf_ostream pfos(this);
        pfos << "Hello world!\n";
    }
};


class pf_ostream
{
public:
    pf_ostream(IContext* context) : session_(context->session()) {}
    // ...

private:
    Session* session_;
    // ...
};

Note how the plugin writer now uses pf_ostream pfos(this) instead of pf_ostream pfos(this->session) and is now oblivious to Session. pf_ostream is also oblivious to Plugin and its descendants.

IContext might not be the best name here. Perhaps IHasSession? (IHasCheezburger???)

Emile Cormier
I like it, but the user still has to explicitly pass the this pointer to the pf_ostream object, which I'd like to avoid if at all possible. There may not be a nice way to do it, I may just have to go with some kind of factory pattern to instantiate the streams...
gct
I think most experienced C++ programmers are used to this sort of thing. Anyone who's done GUI programming is used to passing around some kind of context (window handles, device context) to API functions. I personally would prefer passing a `this` pointer than invoking a magic macro. :-)
Emile Cormier
Yeah unfortunately the folks that will most likely be writing plugins aren't primarily software engineers, so anything I can do to make things "just work" for them is a plus. I'll probably just wrap my streams up in some member functions in Plugin for instantiation.
gct
Yes, a "facade" bound to the session object could be used to instantiate your system facilities (see http://en.wikipedia.org/wiki/Facade_pattern). One possible downside is that your system facilities will have to be allocated on the heap (smart pointers would alleviate memory management headaches, though).
Emile Cormier
+1  A: 

Here's another answer based on the factory idea you mentioned in one of your comments. It uses the Facade pattern to centralize the creation of system facilities in a System class that is bound to a Session:

#include <boost/shared_ptr.hpp>

class System
{
public:
    typedef boost::shared_ptr<pf_ostream> pf_ostream_ptr;
    typedef boost::shared_ptr<pf_istream> pf_istream_ptr;
    // Other system facilities...

    System(Session* session) : session_(session) {}

    pf_ostream_ptr makeOstream()
        {return pf_ostream_ptr(new pf_ostream(session_));}

    pf_istream_ptr makeIstream()
        {return pf_istream_ptr(new pf_istream(session_));}

private:
    Session* session_;
};


class Plugin
{
public:
    Plugin(System* system) : system_(system) {}

    System& system() {return *system_;}

private:
    System* system_;
};


class MyPlugin : public Plugin
{
public:
    MyPlugin(System* system) : Plugin(system) {}

    void print()
    {
        pf_ostream_ptr pfos( system().makeOstream() );
        *pfos << "Hello World!\n";
        // pfos will be deleted automatically at the end of this scope
    }
};

If you have many system facilities, you should make judicious use of forward declarations to avoid long compile times. This solution has the disadvantage of centralizing your system facilities together in a dependency "focal point" (the System class). The System class may need to be changed if it is re-used in another application that uses more or less system facilities.

EDIT:

Here's how you can apply the Proxy pattern (as well as the Pimpl idiom) to get a reference-counted stream class with value semantics:

#include <boost/shared_ptr.hpp>

class pf_ostream
{
public:
    pf_ostream(Session* session);
    pf_ostream& operator<<(int rhs);
    // Use of default copy-constuctor and assignment operator
    //    will work fine because of shared_ptr.

private:
    struct Impl;
    boost::shared_ptr<Impl> pimpl_;
};

// In cpp file

struct pf_ostream::Impl
{
    Impl(Session* session) : session(session) {}
    void insert(int rhs) {/*...*/}
    Session* session;
};

pf_ostream::pf_ostream(Session* session) : pimpl_(new Impl(session)) {}

pf_ostream& pf_ostream::operator<<(int rhs) {pimpl_.insert(rhs); return *this;}

The user will have to be aware that copies of the proxy object will reference the same real stream. Hope this helps.

Emile Cormier
I like it, I'll probably define copying semantics for my streams and return them by value so the user can do something like: pf_ostream outs = make_ostream(); And it'll obviate the memory management stuff.
gct
The problem with streams is that copy semantics don't make sense (I'm thinking std::iostreams). For example, if you have a file stream that is accessing a file, you don't want to have copies made when the stream object is passed as a parameter. However, you could use the Proxy pattern. You'll end up with stack-allocated stream wrapper class that forwards calls to the real heap-allocated stream. You can probably use boost::shared_ptr inside your proxy class to do the memory management. See http://en.wikipedia.org/wiki/Proxy_pattern.
Emile Cormier
Edited answer to show a sample of what I mean by a proxy.
Emile Cormier