Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

boost python make_constructor and custodians and ward

Suppose I have the following two C++ classes (which I can't modify):

struct A
{
    // stuff
};

struct B
{
    // B will internally hold a reference to a
    B(A& a, some_cpp_only_type arg);
};

I am trying to wrap the B class and hiding the some_cpp_only_type argument from the Python interface (there is no alternative constructor for B without this argument).

I have the following wrapper code right now:

using namespace boost::python;

boost::shared_ptr<B> make_B(A& a)
{
    return boost::make_shared<B>(a, get_cpp_only_instance());
}

BOOST_PYTHON_MODULE(my_module)
{
    class_<B, boost::noncopyable>("B", no_init)
      .def("__init__", make_constructor(&make_B));
}

Right now this works because I'm holding a reference to the wrapped A object inside of Python. However, I would really like to keep this around at least until the instance of B is destroyed. I tried adding a with_custodian_and_ward_postcall call policy to make_constructor, but I get several pages of meaningless compiler errors (even from Clang). Here's the modified not working code:

using namespace boost::python;

boost::shared_ptr<B> make_B(A& a)
{
    return boost::make_shared<B>(a, get_cpp_only_instance());
}

BOOST_PYTHON_MODULE(my_module)
{
    class_<B, boost::noncopyable>("B", no_init)
      .def("__init__", make_constructor(&make_B, with_custodian_and_ward_postcall<0,1>()));
}

How do I properly specify the call policy when using make_constructor?

Alternative attempt using make_function

I tried using the solution Tanner Sansbury posted here of wrapping with make_function instead, but this time even though the compile succeeds, I get a ValueError from inside of Python:

No to_python (by-value) converter found for C++ type: A

For reference, here is the code I tried which uses make_function (I get the same error with and without the custodian/ward call policy):

using namespace boost::python;

boost::shared_ptr<B> make_B(A& a)
{
    return boost::make_shared<B>(a, get_cpp_only_instance());
}

void inter_make_B(object self, A& a)
{
    auto constructor = make_constructor(&make_B);
    constructor(self, a);
}

BOOST_PYTHON_MODULE(my_module)
{
    class_<B, boost::noncopyable>("B", no_init)
      .def("__init__", make_function(&inter_make_B, with_custodian_and_ward<1,2>()));
}

How do I properly write the wrapper for B which manages memory correctly without modifying the B class?

like image 503
helloworld922 Avatar asked Jul 05 '16 08:07

helloworld922


1 Answers

I know this post is 3 years old, but I was myself looking for this and unable to find any solution working out of the box with minimal modification of the original code. I finally managed to get it to work thanks to this other post.

Here is a mock based on the snippet you provided:

using namespace boost::python;

boost::shared_ptr<B> make_B(A& a)
{
    return boost::make_shared<B>(a, get_cpp_only_instance());
}

void make_B_wrapper(object self, A& a)
{
    auto constructor = make_constructor(&make_B);
    constructor(self, a);
}

BOOST_PYTHON_MODULE(my_module)
{
    class_<B, boost::noncopyable>("B", no_init)
      .def("__init__", &make_B_wrapper, with_custodian_and_ward_postcall<1,2>());
}

And here is a complete working example demonstrating this approach:

#include <boost/python.hpp>
#include <boost/shared_ptr.hpp> 
#include <boost/make_shared.hpp> 
#include <iostream>

namespace python = boost::python;
  
/// @brief Mockup class with verbose construction and destruction.
class Foo
{
public:
  Foo() { std::cout << "Foo() " << this << std::endl; }
  ~Foo() { std::cout << "~Foo() " << this << std::endl; }
};

/// @brief Mockup class with verbose construction and destruction.
class Bar
{
public:
  Bar() { std::cout << "Bar() " << this << std::endl; }
  ~Bar() { std::cout << "~Bar() " << this << std::endl; }
};

/// @brief Mockup factory function.
boost::shared_ptr<Foo> makeFoo(std::shared_ptr<Bar> & /* unused */)
{
  return boost::make_shared<Foo>();
}

static void makeFooWrapper(python::object & self, std::shared_ptr<Bar> & bar)
{
    auto constructor = python::make_constructor(&makeFoo);
    constructor(self, bar);
}

BOOST_PYTHON_MODULE(example)
{
  python::class_<Bar, std::shared_ptr<Bar>, boost::noncopyable>("Bar", python::init<>());

  python::class_<Foo, std::shared_ptr<Foo>, boost::noncopyable>("Foo", python::no_init)
    .def("__init__", &makeFooWrapper,
                     python::with_custodian_and_ward_postcall<1, 2>());
}
like image 97
milembar Avatar answered Sep 18 '22 12:09

milembar