Silently binding a variable instance to a class in C++?
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?
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.
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???)
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.
精彩评论