Extending Python with C++ singleton pattern using boost python lib

Alex Martelli aleaxit at yahoo.com
Mon Apr 23 05:42:05 EDT 2001


"Jörg Sauer" <joerg2k1 at yahoo.de> wrote in message
news:3ae3d7f5.3475467 at news.t-online.de...
> Hi folks,
> I've got the following problem exposing a c++ singleton pattern to
    [snip]
> What is going wrong???

Hard to say, since the "sample code" you include can't be the
one you're using -- for example this snippet:

> public:
> static CObRegistry& Init()
> CObRegistry& GetReg();

would not compile, as it's not syntactically correct C++ code.  (At
least a semicolon is missing, perhaps another 'static' as well --
it's hard to guess exactly what IS your code, and probably not
all that important).

Presumably, the key issue is that a copy constructor gets invoked
under the covers.  It's hard to avoid that: in general, it's VERY
hard work to wrap/expose/handle non-canonical C++ objects (ones
that can't be default-constructed, destructed, copied, assigned).
Not just in/to Python or in/to Boost Python specifically: non
canonical objects are Bad News in any substantial C++ system.

The good news is that you do NOT need to wrap/expose non canonical
objects.  A more reliable, flexible, sounder pattern is to wrap
and expose "lightweight proxy" C++ objects -- that will internally
delegate whatever is needed to your "one and only substantial
object", but will NOT implement the singleton Design Pattern with
the attendant efforts to ensure only one of them is ever subject
to instantiation (whence the need to avoid copying, etc, etc, and
hence the non-canonicality).


> It seems that the python c++ wrapper instantiate 2 different singleton
> objects. The static member keeps constant and the constructor is
> called only one time, but how can I get two singleton objects then?

Copy constructor appears to be the likeliest culprit.  But if you
forbid copy-constructing, the exposing will probably fail, so that
is likely to be a dead-end.  Don't waste effort trying to expose
non-canonical objects: it's better to spend lesser effort exposing
canonical objects and keep issues such as "there must be only one
instance of this" to the INTERNALS of your system.


> What I want to get is that every call to GetReg() gets the same c++
> object and that python calls like reg1.setObject(40);
> reg2.setObject(10); change the same c++ memory thow it doesn't matter
> through which. Python object I store or retrieve date.

You can get the part from "and that" onwards by giving up the
strict letter of the first line of this paragraph -- let a
thousand C++ objects bloom as long as each one is a very
lightweight thingy that just delegates everything (specifically
all of its state) to the "real" singly-instantiated object
(which then doesn't need to implement the Singleton DP any
more).

> Any suggestions?

Over the last week or so, the singleton DP came up for discussion
a couple of times on this group -- I seem to be in a tiny minority
in disliking it intensely in favour of featherweight proxies, though
I know I'm NOT the only one since I recall a C++ Report article
that made points very similar to mine.  www.google.com/groups should
let you fetch and read the few relevant articles for background.

Leaving your sample code alone (assuming needed changes are made
so it will compile), say it comes from a C++ library you want to
wrap and NOT modify, I would suggest a few changes as follows:


> ------ Sample Code ----------
>
>
> #include <iostream>
> #include <string>
> using std::cout;
> using std::endl;
>
>
> #include <boost/python/class_builder.hpp>
> namespace python = boost::python;
>
>
> namespace mein{ // Avoid cluttering the global namespace.
>
> class CObRegistry  // Singleton class
> {
> // Singleton
> private:
> static CObRegistry* m_pObRegistry; protected:
> CObRegistry();
> public:
> static CObRegistry& Init()
> CObRegistry& GetReg();

I assume GetReg is a _static_ function of CObRegistry.

> // My functions
> protected:
> int m_TestObj;
> public:
> int getObject();
> void setObject(int);
> };
    [snip]
Add the following for example:

    struct CObRegistryFeatherweightProxy {
        int getObject() {
            return CObRegistry::GetReg().getObject();
        }
        void setObject(int i) {
            CObRegistry::GetReg().setObject(i);
        }
    };

    CObRegistryFeatherweightProxy GetRegProxy() {
        return CObRegistryFeatherweightProxy();
    }

Now the expose-to-Python part:

>     // Create the Python type object for our extension class.
>     python::class_builder<CObRegistry> CObRegistry_class(this_module,
> "ObRegistry");

becomes
    python::class_builder<CObRegistryFeatherweightProxy> CObRegistry_class
etc;

> //Add memberfunctions of myObj_class,singleton_class
> CObRegistry_class.def(&CObRegistry::getObject, "getObject");
> CObRegistry_class.def(&CObRegistry::setObject, "setObject");
> CObRegistry_class.def(&CObRegistry::Init, "Init");

remove the third one of these and change the other two to use
methods from CObRegistryFeatherweightProxy -- I don't think you
need to expose the Init as it's called from within GetReg, but,
if you want, you can wrap that into another function anyway.


> //Add normal function
> this_module.def(GetReg,"GetReg");

This too wouldn't compile I think, but anyway it now becomes
    this_module.def(GetRegProxy, "GetReg");
and does compile since GetRegProxy is a global function (in
your sample it seems to me CObRegistry::GetReg would have
been needed here, else the code shouldn't compile, surely).


I haven't tested this exact set of mods, but, anyway, this
general approach has long served me well any time I've had
to wrap and expose C++ classes, even just from one subsystem
to another when both coded in C++, much more when such
things as COM or Python or other cross-language passages
were involved.  I _strongly_ suggest you expose canonical
stateless objects rather than "fighting City Hall" by striving
to expose non-canonical objects at any subsystem interface!


Alex






More information about the Python-list mailing list