[C++-sig] pybindgen: allow_subclassing option causing reference parameters to be copied?

Gustavo Carneiro gjcarneiro at gmail.com
Thu Jun 25 00:31:29 CEST 2009


2009/6/24 J. Michael Owen <mikeowen at llnl.gov>

> Hi again,
>
> I've run across another issue confusing me as I try to use pybindgen, in
> this case it's the treatment of reference parameters in virtual methods
> where you're allowing the class to be subclassed from python.  It looks like
> the helper class that is generated is making copies of parameters that are
> being passed as references, which is not what we want.  Take the following
> example C++ classes:
>
> class A {
> public:
>  A(const int val): mVal(val) {}
> private:
>  int mVal;
>  A();
>  A(const A& rhs);
>  A& operator=(const A& rhs);
> };
>
> class B {
> public:
>  B() {}
>  virtual ~B() {}
>  virtual void some_virtual_method(const A& a) const { std::cerr << &a <<
> std::endl; }
> private:
> };
>
> I'd like to be able to create subclasses of B from python, so I try
> wrapping these classes with the following pybindgen code:
>
> mod = Module("ref_param_example")
> mod.add_include('"classes.hh"')
> a = mod.add_class("A")
> b = mod.add_class("B", allow_subclassing=True)
> a.add_constructor([param("int", "val")])
> b.add_constructor([])
> b.add_method("some_virtual_method", None, [param("A&", "a")],
> is_const=True, is_virtual=True)
>
> The resulting generated code contains the following:
>
> void
> PyB__PythonHelper::some_virtual_method(A & a) const
> {
>    PyGILState_STATE __py_gil_state;
>    B *self_obj_before;
>    PyObject *py_retval;
>    PyA *py_A;
>
>    __py_gil_state = (PyEval_ThreadsInitialized() ? PyGILState_Ensure() :
> (PyGILState_STATE) 0);
>    if (!PyObject_HasAttrString(m_pyself, (char *) "_some_virtual_method"))
> {
>        B::some_virtual_method(a);
>    if (PyEval_ThreadsInitialized())
>        PyGILState_Release(__py_gil_state);
>        return;
>    }
>    self_obj_before = reinterpret_cast< PyB* >(m_pyself)->obj;
>    reinterpret_cast< PyB* >(m_pyself)->obj = const_cast< B* >((const B*)
> this);
>    py_A = PyObject_New(PyA, &PyA_Type);
>    py_A->obj = new A(a);
> ....
>
> As you can see, the last line quoted here is trying to make a copy of the
> instance "a" that was passed in.  In this example this will not compile
> because the copy constructor for A is private.  In general we don't want to
> make copies of reference parameters regardless of course.
>
> Am I once again missing something here?  Is there some way I can declare
> that the passed parameters for "some_virtual_method" should really be
> treated as references and not copied?
>

Sorry, but in this case the answer is no.  The virtual method wrapper here
receives a parameter from C++ and has to create a Python wrapper for it.
The wrapper needs to own the object.  The reason is that the Python method
that is called may decide to keep a reference to the object.  If the Python
wrapper kept a "shared" reference to the same C++ object then it could at
some point in time be keeping a reference to an object when the caller has
already deallocated it.

To summarize, the object copy is done because the alternative would be
memory unsafe.

This is one case where a C++ API blindly designed without considering
language bindings may make it difficult to bind later on.  Ideally, in this
case you should pass a pointer to A, and even better A would have reference
counting so that both caller and callee could own a reference to the same
object at the same time...

-- 
Gustavo J. A. M. Carneiro
INESC Porto, Telecommunications and Multimedia Unit
"The universe is always one step beyond logic." -- Frank Herbert
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.python.org/pipermail/cplusplus-sig/attachments/20090624/dbaa7fdf/attachment-0001.htm>


More information about the Cplusplus-sig mailing list