Problem
I have an abstract base class in c++, IFunctor, that I wish to write concrete classes for in python.
These python classes then need to be added back into a std::vector<std::shared_ptr<IFunctor> > that is held in the c++ FunctorContainer class, and can then be used from within c++.
All wrapping is provided for by boost::python.
Challenges
- We use
std::shared_ptrextensively in the c++, so simply switching toboost::shared_ptrdoesn't make sense. - I don't think you can write a python function to create a
std::shared_ptrof a python class -- maybe someone knows how to do this?
Solution
I have tried to bring in many sources from the web to come up with a solution. It works (at least on the surface), but I don't fully understand why. I'm hoping there are people here that might be able to explain:
- Is this a reasonable solution? How might it break?
- Is there a better way?
- How does it actually work? Is it just to do with
boost::pythonmagic under the hood withboost::shared_ptr?
Working example
// pyfunctor.cpp
#include <memory>
#include <exception>
#include <boost/python.hpp>
/** Abstract base class */
class IFunctor
{
public:
virtual void apply() const = 0;
};
/** Concrete c++ class */
class CPPFunctor :
public IFunctor
{
public:
void apply() const override
{
std::cout << "CPPFunctor::apply()" << std::endl;
}
static
std::shared_ptr<IFunctor> create()
{
return std::make_shared<CPPFunctor>();
}
};
/** Container for the functors, store functors in vector */
class FunctorContainer
{
public:
void addFunctor(
const std::shared_ptr<IFunctor>& functor
)
{
mFunctors.push_back(functor);
}
void test() const
{
for (const auto& functor : mFunctors) {
functor->apply();
}
}
private:
std::vector<std::shared_ptr<IFunctor> > mFunctors;
};
/** Wrapper class to enable inheritance of abstract base
* class from within python */
class IFunctorWrapper :
public IFunctor,
public boost::python::wrapper<IFunctor>
{
public:
PyObject* self;
IFunctorWrapper(PyObject* tmpSelf) :
self(tmpSelf) {}
void apply() const
{
try {
return boost::python::call_method<void>(self, "apply");
} catch (const boost::python::error_already_set& e) {
// handle python exception
throw "Python failed";
}
}
};
/** Helps in the switching of boost::shared_ptr to std::shared_ptr */
template<typename T>
void do_release(typename boost::shared_ptr<T> const&, T*) {}
/**
* Boost can handle python objects as boost::shared_ptr
* http://ift.tt/1LsY2lk
*/
void
addPythonFunctor(
FunctorContainer& container,
const boost::shared_ptr<IFunctor>& functor
)
{
// convert from boost::shared_ptr to std::shared_ptr
// http://ift.tt/1LsY2lm
std::shared_ptr<IFunctor> stdPtr(
functor.get(),
boost::bind(&do_release<IFunctor>, functor, _1));
assert(stdPtr);
container.addFunctor(stdPtr);
}
/** helper for wrapping both pointer types */
template<typename T>
void
registerPtrToPython()
{
boost::python::register_ptr_to_python<std::shared_ptr<T> >();
boost::python::register_ptr_to_python<boost::shared_ptr<T> >();
}
/** primary boost::python code */
BOOST_PYTHON_MODULE(pyfunctor)
{
using namespace boost::python;
// wrap the abstract base class
class_<IFunctor, IFunctorWrapper, boost::noncopyable>("IFunctor")
.def("apply", &IFunctorWrapper::apply)
;
registerPtrToPython<IFunctor>();
// wrap the C++ child class
class_<CPPFunctor, bases<IFunctor>, boost::noncopyable>("CPPFunctor", no_init)
.def("create", &CPPFunctor::create)
.staticmethod("create")
;
registerPtrToPython<CPPFunctor>();
// wrap the container
class_<FunctorContainer, boost::noncopyable>("FunctorContainer")
.def("addFunctor", &FunctorContainer::addFunctor) // add c++ created functors
.def("addPythonFunctor", &addPythonFunctor) // add python created functors
.def("test", &FunctorContainer::test)
;
}
A Makefile for compiling
# Makefile
PYTHON_VERSION = 2.7
PYTHON_INCLUDE = /usr/include/python$(PYTHON_VERSION)
BOOST_INC = /usr/include
BOOST_LIB = /usr/lib
TARGET = pyfunctor
$(TARGET).so: $(TARGET).o
g++ -shared -Wl,--export-dynamic $(TARGET).o -L$(BOOST_LIB) -lboost_python -L/usr/lib/python$(PYTHON_VERSION)/config -lpython$(PYTHON_VERSION) -o $(TARGET).so
$(TARGET).o: $(TARGET).cpp
g++ -Wall -std=c++11 -I$(PYTHON_INCLUDE) -I$(BOOST_INC) -fPIC -c $(TARGET).cpp
A test script that shows the example working
# test.py
from pyfunctor import IFunctor, CPPFunctor, FunctorContainer
class PythonFunctor(IFunctor):
def __init__(self):
super(PythonFunctor, self).__init__()
def apply(self):
print "PythonFunctor::apply()"
container = FunctorContainer()
#functor = CPPFunctor() # this would fail due to no_init
functor = CPPFunctor.create() # creates a std::shared_ptr
container.addFunctor(functor)
functor = PythonFunctor() # can be used as a boost::shared_ptr (?)
container.addPythonFunctor(functor)
container.test()
Aucun commentaire:
Enregistrer un commentaire