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
_______________________________________________
Cplusplus-sig mailing list
Cplusplus-sig@python.org
https://mail.python.org/mailman/listinfo/cplusplus-sig

Reply via email to