Discussion:
[C++-sig] Call policy to store reference
Bruce Merry
2015-09-27 11:38:50 UTC
Permalink
Hi

I don't know if something like this has been discussed before, but I
thought I'd contribute it in case it is useful.

I found that with_custodian_and_ward didn't suit my needs, because
(a) the association it creates is permanent, even if the underlying
association is later broken, leading to handle leaks;
(b) I had some issues with destructor ordering when the interpreter
exits (sorry, I don't recall the details).

I've created an alternative call policy that requires a wrapper
custodian class with a handle<> data member, which is set to a
reference to the ward object. This is suitable for wrapping functions
that set an underlying C++ reference in the custodian, replacing any
previous reference rather than adding to it.

The code is below - I've only written a _postcall version, but it
should be possible to write a non-postcall version too. At the moment
it is part of a GPLv3+ project, but I'm working on getting permission
from my employer to release this code under the Boost License as well,
to make it easier to use in other Boost.Python-based projects (or to
incorporate into Boost.Python itself). You can see it in action in
spead2 (e.g. https://github.com/ska-sa/spead2/blob/v0.3.0/src/py_send.cpp).


template<typename T, boost::python::handle<> T::*handle_ptr,
std::size_t custodian, std::size_t ward,
class BasePolicy_ = boost::python::default_call_policies>
struct store_handle_postcall : BasePolicy_
{
static_assert(custodian != ward, "object must not hold reference
to itself");

static PyObject* postcall(PyObject *args, PyObject *result)
{
std::size_t arity = PyTuple_GET_SIZE(args);
if (custodian > arity || ward > arity)
{
PyErr_SetString(PyExc_IndexError,
"store_handle_postcall: argument index out
of range");
return nullptr;
}

result = BasePolicy_::postcall(args, result);
if (result == nullptr)
return nullptr;

PyObject *owner = custodian > 0 ? PyTuple_GET_ITEM(args,
custodian - 1) : result;
PyObject *child = ward > 0 ? PyTuple_GET_ITEM(args, ward - 1) : result;
boost::python::extract<T &> extractor(owner);
try
{
T &target = extractor();
target.*handle_ptr =
boost::python::handle<>(boost::python::borrowed(child));
}
catch (boost::python::error_already_set)
{
Py_XDECREF(result);
return nullptr;
}
return result;
}
};


Bruce
--
Bruce Merry
Senior Science Processing Developer
SKA South Africa
Loading...