[C++-sig] Boost python exposing abstract class , function returning boost::shared_ptr

Holger Joukl Holger.Joukl at LBBW.de
Mon Mar 30 11:09:13 CEST 2015


Hi,

> Thank you for the reply.
> It compiles but same error on execution.

If I change the A2 exposure to

    bp::class_<A2, boost::shared_ptr<A2>, boost::noncopyable>("A2",
bp::no_init)
    ...

then f1() successfully returns a B2 object. But of course we then lose
the Python-callback abilities provided by the wrapper class.

Looking into the Boost.Python tests i stumbled across shared_ptr.cpp
which contains:
    ...
    // This is the ugliness required to register a to-python converter
    // for shared_ptr<A>.
    objects::class_value_wrapper<
        shared_ptr<A>
      , objects::make_ptr_instance<A,
objects::pointer_holder<shared_ptr<A>,A> >
    >();
    ...


So, giving this a try - looks like the following seems to be a working
version for your sample
(note that I changed the extension module name to shared_ptr_hierarchy
in my code):


C++ (wrapper) code:
==================

#include <boost/python.hpp>

namespace bp = boost::python;


struct A2
{
    virtual ~A2(){}
    virtual int ret() =0;
};


struct B2: public A2
{
    virtual ~B2(){}
    int ret() { return 1; }
};


// wrapper
struct  A2Wrap : A2, bp::wrapper<A2>
{
    inline int ret()
    {
        return this->get_override("ret")();
    }
};


// To the best of my knowledge we need to add callback support
// through a wrapper class at every level of the hierarchy, so
// a bit of code duplication going on. This is due to the fact
// that B2 has no inheritance relation whatsoever to A2Wrap.
// I haven't so far found an elegant/short solution for this
// (e.g. using templates or macros)
struct  B2Wrap : B2, bp::wrapper<B2>
{
    inline int ret()
    {
        if (bp::override py_override = this->get_override("ret")) {
            return py_override();
        }
        return B2::ret();
    }

    inline int default_ret()
    {
        return B2::ret();
    }
};


// function to map
boost::shared_ptr<A2> f1()
{
    return  boost::shared_ptr<A2>(new B2());
}


// invoke overridden virtual from C++ side (check callback-ability)
int invoke_ret(A2 & a2)
{
    return a2.ret();
}


// expose to python
BOOST_PYTHON_MODULE(shared_ptr_hierarchy)
{

    bp::class_<A2Wrap, boost::shared_ptr<A2Wrap>, boost::noncopyable>("A2")
        .def("ret", bp::pure_virtual(&A2::ret))
        ;

    // taken from boost.python libs/python/test/shared_ptr.cpp:
    // This is the ugliness required to register a to-python converter
    // for shared_ptr<A>.
    bp::objects::class_value_wrapper<
        boost::shared_ptr<A2>
      , bp::objects::make_ptr_instance<A2,
bp::objects::pointer_holder<boost::shared_ptr<A2>,A2> >
    >();

    bp::class_<B2Wrap, boost::shared_ptr<B2Wrap>, bp::bases<A2>,
boost::noncopyable >("B2")
        .def("ret", &B2::ret, &B2Wrap::default_ret)
        ;

    bp::def("f1", f1);

    bp::def("invoke_ret", &invoke_ret, (bp::arg("a2")));
}


Python test code:
=================

import shared_ptr_hierarchy


class PyB2(shared_ptr_hierarchy.A2):
    def ret(self):
        print "PyB2 Python class", self
        return 5

class PyC2(shared_ptr_hierarchy.B2):
    def ret(self):
        print "PyC2 Python class", self
        return 6


def invoke(factory):
    print "\nfactory:", factory
    obj = factory()
    print "obj =", obj
    print "obj.ret() ->", obj.ret()
    if hasattr(shared_ptr_hierarchy, 'invoke_ret'):
        print "shared_ptr_hierarchy.invoke_ret(obj) ->", \
              shared_ptr_hierarchy.invoke_ret(obj)

try:
    invoke(shared_ptr_hierarchy.A2)
except Exception as e:
    print "Exception:", e
invoke(shared_ptr_hierarchy.B2)
invoke(shared_ptr_hierarchy.f1)
invoke(PyB2)
invoke(PyC2)


Test code output:
=================

factory: <class 'shared_ptr_hierarchy.A2'>
obj = <shared_ptr_hierarchy.A2 object at 0x17cb5d0>
obj.ret() -> Exception: Pure virtual function called

factory: <class 'shared_ptr_hierarchy.B2'>
obj = <shared_ptr_hierarchy.B2 object at 0x17cb628>
obj.ret() -> 1
shared_ptr_hierarchy.invoke_ret(obj) -> 1

factory: <Boost.Python.function object at 0x177a460>
obj = <shared_ptr_hierarchy.B2 object at 0x17d0668>
obj.ret() -> 1
shared_ptr_hierarchy.invoke_ret(obj) -> 1

factory: <class '__main__.PyB2'>
obj = <__main__.PyB2 object at 0x17cb628>
obj.ret() -> PyB2 Python class <__main__.PyB2 object at 0x17cb628>
5
shared_ptr_hierarchy.invoke_ret(obj) -> PyB2 Python class <__main__.PyB2
object at 0x17cb628>
5

factory: <class '__main__.PyC2'>
obj = <__main__.PyC2 object at 0x17cb628>
obj.ret() -> PyC2 Python class <__main__.PyC2 object at 0x17cb628>
6
shared_ptr_hierarchy.invoke_ret(obj) -> PyC2 Python class <__main__.PyC2
object at 0x17cb628>
6


I can't really say that I understand why that's needed, but alas...

Open issues:
- Would be nice to have the abstract class A2 not being initializable from
Python,
but we can't use bp::no_init and inherit from A2 in Python because the
Python-derived
needs to call the (A2) base class constructor


Holger

Landesbank Baden-Wuerttemberg
Anstalt des oeffentlichen Rechts
Hauptsitze: Stuttgart, Karlsruhe, Mannheim, Mainz
HRA 12704
Amtsgericht Stuttgart



More information about the Cplusplus-sig mailing list