mirror of
https://github.com/pybind/pybind11.git
synced 2024-11-25 14:45:12 +00:00
keep_alive call policy (analogous to Boost.Python's with_custodian_and_ward, fixes #62)
This commit is contained in:
parent
87187afe91
commit
5f218b3f2c
@ -109,6 +109,7 @@ add_library(example SHARED
|
|||||||
example/example10.cpp
|
example/example10.cpp
|
||||||
example/example11.cpp
|
example/example11.cpp
|
||||||
example/example12.cpp
|
example/example12.cpp
|
||||||
|
example/example13.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
# Don't add a 'lib' prefix to the shared library
|
# Don't add a 'lib' prefix to the shared library
|
||||||
@ -179,7 +180,7 @@ endif()
|
|||||||
|
|
||||||
enable_testing()
|
enable_testing()
|
||||||
set(RUN_TEST ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/example/run_test.py)
|
set(RUN_TEST ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/example/run_test.py)
|
||||||
foreach(i RANGE 1 12)
|
foreach(i RANGE 1 13)
|
||||||
add_test(NAME example${i} COMMAND ${RUN_TEST} example${i})
|
add_test(NAME example${i} COMMAND ${RUN_TEST} example${i})
|
||||||
endforeach()
|
endforeach()
|
||||||
|
|
||||||
|
@ -446,6 +446,33 @@ See below for an example that uses the
|
|||||||
return m.ptr();
|
return m.ptr();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Additional call policies
|
||||||
|
========================
|
||||||
|
|
||||||
|
In addition to the above return value policies, further `call policies` can be
|
||||||
|
specified to indicate dependencies between parameters. There is currently just
|
||||||
|
one policy named ``keep_alive<Nurse, Patient>``, which indicates that the
|
||||||
|
argument with index ``Patient`` should be kept alive at least until the
|
||||||
|
argument with index ``Nurse`` is freed by the garbage collector; argument
|
||||||
|
indices start at one, while zero refers to the return value. Arbitrarily many
|
||||||
|
call policies can be specified.
|
||||||
|
|
||||||
|
For instance, binding code for a a list append operation that ties the lifetime
|
||||||
|
of the newly added element to the underlying container might be declared as
|
||||||
|
follows:
|
||||||
|
|
||||||
|
.. code-block:: cpp
|
||||||
|
|
||||||
|
py::class_<List>(m, "List")
|
||||||
|
.def("append", &List::append, py::keep_alive<1, 2>());
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
``keep_alive`` is analogous to the ``with_custodian_and_ward`` (if Nurse,
|
||||||
|
Patient != 0) and ``with_custodian_and_ward_postcall`` (if Nurse/Patient ==
|
||||||
|
0) policies from Boost.Python.
|
||||||
|
|
||||||
Implicit type conversions
|
Implicit type conversions
|
||||||
=========================
|
=========================
|
||||||
|
|
||||||
|
@ -21,6 +21,7 @@ void init_ex9(py::module &);
|
|||||||
void init_ex10(py::module &);
|
void init_ex10(py::module &);
|
||||||
void init_ex11(py::module &);
|
void init_ex11(py::module &);
|
||||||
void init_ex12(py::module &);
|
void init_ex12(py::module &);
|
||||||
|
void init_ex13(py::module &);
|
||||||
|
|
||||||
PYBIND11_PLUGIN(example) {
|
PYBIND11_PLUGIN(example) {
|
||||||
py::module m("example", "pybind example plugin");
|
py::module m("example", "pybind example plugin");
|
||||||
@ -37,6 +38,7 @@ PYBIND11_PLUGIN(example) {
|
|||||||
init_ex10(m);
|
init_ex10(m);
|
||||||
init_ex11(m);
|
init_ex11(m);
|
||||||
init_ex12(m);
|
init_ex12(m);
|
||||||
|
init_ex13(m);
|
||||||
|
|
||||||
return m.ptr();
|
return m.ptr();
|
||||||
}
|
}
|
||||||
|
37
example/example13.cpp
Normal file
37
example/example13.cpp
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
/*
|
||||||
|
example/example13.cpp -- keep_alive modifier (pybind11's version
|
||||||
|
of Boost.Python's with_custodian_and_ward / with_custodian_and_ward_postcall)
|
||||||
|
|
||||||
|
Copyright (c) 2015 Wenzel Jakob <wenzel@inf.ethz.ch>
|
||||||
|
|
||||||
|
All rights reserved. Use of this source code is governed by a
|
||||||
|
BSD-style license that can be found in the LICENSE file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "example.h"
|
||||||
|
|
||||||
|
class Child {
|
||||||
|
public:
|
||||||
|
Child() { std::cout << "Allocating child." << std::endl; }
|
||||||
|
~Child() { std::cout << "Releasing child." << std::endl; }
|
||||||
|
};
|
||||||
|
|
||||||
|
class Parent {
|
||||||
|
public:
|
||||||
|
Parent() { std::cout << "Allocating parent." << std::endl; }
|
||||||
|
~Parent() { std::cout << "Releasing parent." << std::endl; }
|
||||||
|
void addChild(Child *) { }
|
||||||
|
Child *returnChild() { return new Child(); }
|
||||||
|
};
|
||||||
|
|
||||||
|
void init_ex13(py::module &m) {
|
||||||
|
py::class_<Parent>(m, "Parent")
|
||||||
|
.def(py::init<>())
|
||||||
|
.def("addChild", &Parent::addChild)
|
||||||
|
.def("addChildKeepAlive", &Parent::addChild, py::keep_alive<1, 2>())
|
||||||
|
.def("returnChild", &Parent::returnChild)
|
||||||
|
.def("returnChildKeepAlive", &Parent::returnChild, py::keep_alive<1, 0>());
|
||||||
|
|
||||||
|
py::class_<Child>(m, "Child")
|
||||||
|
.def(py::init<>());
|
||||||
|
}
|
46
example/example13.py
Normal file
46
example/example13.py
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
from __future__ import print_function
|
||||||
|
import sys
|
||||||
|
import gc
|
||||||
|
sys.path.append('.')
|
||||||
|
|
||||||
|
from example import Parent, Child
|
||||||
|
|
||||||
|
if True:
|
||||||
|
p = Parent()
|
||||||
|
p.addChild(Child())
|
||||||
|
gc.collect()
|
||||||
|
print(p)
|
||||||
|
p = None
|
||||||
|
|
||||||
|
gc.collect()
|
||||||
|
print("")
|
||||||
|
|
||||||
|
if True:
|
||||||
|
p = Parent()
|
||||||
|
p.returnChild()
|
||||||
|
gc.collect()
|
||||||
|
print(p)
|
||||||
|
p = None
|
||||||
|
|
||||||
|
gc.collect()
|
||||||
|
print("")
|
||||||
|
|
||||||
|
if True:
|
||||||
|
p = Parent()
|
||||||
|
p.addChildKeepAlive(Child())
|
||||||
|
gc.collect()
|
||||||
|
print(p)
|
||||||
|
p = None
|
||||||
|
gc.collect()
|
||||||
|
print("")
|
||||||
|
|
||||||
|
if True:
|
||||||
|
p = Parent()
|
||||||
|
p.returnChildKeepAlive()
|
||||||
|
gc.collect()
|
||||||
|
print(p)
|
||||||
|
p = None
|
||||||
|
|
||||||
|
gc.collect()
|
||||||
|
print("")
|
||||||
|
print("Terminating..")
|
25
example/example13.ref
Normal file
25
example/example13.ref
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
Allocating parent.
|
||||||
|
Allocating child.
|
||||||
|
Releasing child.
|
||||||
|
<example.Parent object at 0x10eb726c0>
|
||||||
|
Releasing parent.
|
||||||
|
|
||||||
|
Allocating parent.
|
||||||
|
Allocating child.
|
||||||
|
Releasing child.
|
||||||
|
<example.Parent object at 0x10eb726c0>
|
||||||
|
Releasing parent.
|
||||||
|
|
||||||
|
Allocating parent.
|
||||||
|
Allocating child.
|
||||||
|
<example.Parent object at 0x10eb726c0>
|
||||||
|
Releasing parent.
|
||||||
|
Releasing child.
|
||||||
|
|
||||||
|
Allocating parent.
|
||||||
|
Allocating child.
|
||||||
|
<example.Parent object at 0x10eb726c0>
|
||||||
|
Releasing parent.
|
||||||
|
Releasing child.
|
||||||
|
|
||||||
|
Terminating..
|
@ -167,6 +167,7 @@ template <typename type, typename holder_type = std::unique_ptr<type>> struct in
|
|||||||
PyObject_HEAD
|
PyObject_HEAD
|
||||||
type *value;
|
type *value;
|
||||||
PyObject *parent;
|
PyObject *parent;
|
||||||
|
PyObject *weakrefs;
|
||||||
bool owned : 1;
|
bool owned : 1;
|
||||||
bool constructed : 1;
|
bool constructed : 1;
|
||||||
holder_type holder;
|
holder_type holder;
|
||||||
|
@ -58,6 +58,28 @@ struct name { const char *value; name(const char *value) : value(value) { } };
|
|||||||
/// Annotation for function siblings
|
/// Annotation for function siblings
|
||||||
struct sibling { PyObject *value; sibling(handle value) : value(value.ptr()) { } };
|
struct sibling { PyObject *value; sibling(handle value) : value(value.ptr()) { } };
|
||||||
|
|
||||||
|
/// Keep patient alive while nurse lives
|
||||||
|
template <int Nurse, int Patient> struct keep_alive { };
|
||||||
|
|
||||||
|
NAMESPACE_BEGIN(detail)
|
||||||
|
template <typename... Args> struct process_dynamic;
|
||||||
|
template <typename T> struct process_dynamic<T> {
|
||||||
|
static void precall(PyObject *) { }
|
||||||
|
static void postcall(PyObject *, PyObject *) { }
|
||||||
|
};
|
||||||
|
template <> struct process_dynamic<> : public process_dynamic<void> { };
|
||||||
|
template <typename T, typename... Args> struct process_dynamic<T, Args...> {
|
||||||
|
static void precall(PyObject *arg) {
|
||||||
|
process_dynamic<T>::precall(arg);
|
||||||
|
process_dynamic<Args...>::precall(arg);
|
||||||
|
}
|
||||||
|
static void postcall(PyObject *arg, PyObject *ret) {
|
||||||
|
process_dynamic<T>::postcall(arg, ret);
|
||||||
|
process_dynamic<Args...>::postcall(arg, ret);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
NAMESPACE_END(detail)
|
||||||
|
|
||||||
/// Wraps an arbitrary C++ function/method/lambda function/.. into a callable Python object
|
/// Wraps an arbitrary C++ function/method/lambda function/.. into a callable Python object
|
||||||
class cpp_function : public function {
|
class cpp_function : public function {
|
||||||
private:
|
private:
|
||||||
@ -110,6 +132,8 @@ private:
|
|||||||
(void) unused;
|
(void) unused;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <int Nurse, int Patient>
|
||||||
|
static void process_extra(const keep_alive<Nurse, Patient> &, function_entry *) { }
|
||||||
static void process_extra(const char *doc, function_entry *entry) { entry->doc = (char *) doc; }
|
static void process_extra(const char *doc, function_entry *entry) { entry->doc = (char *) doc; }
|
||||||
static void process_extra(const pybind11::doc &d, function_entry *entry) { entry->doc = (char *) d.value; }
|
static void process_extra(const pybind11::doc &d, function_entry *entry) { entry->doc = (char *) d.value; }
|
||||||
static void process_extra(const pybind11::name &n, function_entry *entry) { entry->name = (char *) n.value; }
|
static void process_extra(const pybind11::name &n, function_entry *entry) { entry->name = (char *) n.value; }
|
||||||
@ -154,7 +178,10 @@ public:
|
|||||||
cast_in args;
|
cast_in args;
|
||||||
if (!args.load(pyArgs, true))
|
if (!args.load(pyArgs, true))
|
||||||
return (PyObject *) 1; /* Special return code: try next overload */
|
return (PyObject *) 1; /* Special return code: try next overload */
|
||||||
return cast_out::cast(args.template call<Return>((Return (*)(Args...)) entry->data), entry->policy, parent);
|
detail::process_dynamic<Extra...>::precall(pyArgs);
|
||||||
|
PyObject *result = cast_out::cast(args.template call<Return>((Return (*)(Args...)) entry->data), entry->policy, parent);
|
||||||
|
detail::process_dynamic<Extra...>::postcall(pyArgs, result);
|
||||||
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
process_extras(std::make_tuple(std::forward<Extra>(extra)...), m_entry);
|
process_extras(std::make_tuple(std::forward<Extra>(extra)...), m_entry);
|
||||||
@ -210,7 +237,10 @@ private:
|
|||||||
cast_in args;
|
cast_in args;
|
||||||
if (!args.load(pyArgs, true))
|
if (!args.load(pyArgs, true))
|
||||||
return (PyObject *) 1; /* Special return code: try next overload */
|
return (PyObject *) 1; /* Special return code: try next overload */
|
||||||
return cast_out::cast(args.template call<Return>(((capture *) entry->data)->f), entry->policy, parent);
|
detail::process_dynamic<Extra...>::precall(pyArgs);
|
||||||
|
PyObject *result = cast_out::cast(args.template call<Return>(((capture *) entry->data)->f), entry->policy, parent);
|
||||||
|
detail::process_dynamic<Extra...>::postcall(pyArgs, result);
|
||||||
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
process_extras(std::make_tuple(std::forward<Extra>(extra)...), m_entry);
|
process_extras(std::make_tuple(std::forward<Extra>(extra)...), m_entry);
|
||||||
@ -568,8 +598,10 @@ public:
|
|||||||
type->ht_type.tp_as_sequence = &type->as_sequence;
|
type->ht_type.tp_as_sequence = &type->as_sequence;
|
||||||
type->ht_type.tp_as_mapping = &type->as_mapping;
|
type->ht_type.tp_as_mapping = &type->as_mapping;
|
||||||
type->ht_type.tp_base = (PyTypeObject *) parent;
|
type->ht_type.tp_base = (PyTypeObject *) parent;
|
||||||
|
type->ht_type.tp_weaklistoffset = offsetof(instance<void>, weakrefs);
|
||||||
|
|
||||||
if (doc) {
|
if (doc) {
|
||||||
size_t size = strlen(doc)+1;
|
size_t size = strlen(doc) + 1;
|
||||||
type->ht_type.tp_doc = (char *) PyObject_MALLOC(size);
|
type->ht_type.tp_doc = (char *) PyObject_MALLOC(size);
|
||||||
memcpy((void *) type->ht_type.tp_doc, doc, size);
|
memcpy((void *) type->ht_type.tp_doc, doc, size);
|
||||||
}
|
}
|
||||||
@ -654,6 +686,8 @@ protected:
|
|||||||
registered_instances.erase(it);
|
registered_instances.erase(it);
|
||||||
}
|
}
|
||||||
Py_XDECREF(self->parent);
|
Py_XDECREF(self->parent);
|
||||||
|
if (self->weakrefs)
|
||||||
|
PyObject_ClearWeakRefs((PyObject *) self);
|
||||||
}
|
}
|
||||||
Py_TYPE(self)->tp_free((PyObject*) self);
|
Py_TYPE(self)->tp_free((PyObject*) self);
|
||||||
}
|
}
|
||||||
@ -927,6 +961,37 @@ template <typename... Args> struct init {
|
|||||||
class_.def("__init__", [](Base *instance, Args... args) { new (instance) Base(args...); }, std::forward<Extra>(extra)...);
|
class_.def("__init__", [](Base *instance, Args... args) { new (instance) Base(args...); }, std::forward<Extra>(extra)...);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
PYBIND11_NOINLINE inline void keep_alive_impl(int Nurse, int Patient, PyObject *arg, PyObject *ret) {
|
||||||
|
/* Clever approach based on weak references taken from Boost.Python */
|
||||||
|
PyObject *nurse = Nurse > 0 ? PyTuple_GetItem(arg, Nurse - 1) : ret;
|
||||||
|
PyObject *patient = Patient > 0 ? PyTuple_GetItem(arg, Patient - 1) : ret;
|
||||||
|
|
||||||
|
if (nurse == nullptr || patient == nullptr)
|
||||||
|
throw std::runtime_error("Could not activate keep_alive");
|
||||||
|
|
||||||
|
cpp_function disable_lifesupport(
|
||||||
|
[patient](handle weakref) { Py_DECREF(patient); weakref.dec_ref(); }
|
||||||
|
);
|
||||||
|
|
||||||
|
PyObject *weakref = PyWeakref_NewRef(nurse, disable_lifesupport.ptr());
|
||||||
|
if (weakref == nullptr)
|
||||||
|
throw std::runtime_error("Could not allocate weak reference!");
|
||||||
|
|
||||||
|
Py_INCREF(patient); /* reference patient and leak the weak reference */
|
||||||
|
}
|
||||||
|
|
||||||
|
template <int Nurse, int Patient> struct process_dynamic<keep_alive<Nurse, Patient>> : public process_dynamic<void> {
|
||||||
|
template <int N = Nurse, int P = Patient, typename std::enable_if<N != 0 && P != 0, int>::type = 0>
|
||||||
|
static void precall(PyObject *arg) { keep_alive_impl(Nurse, Patient, arg, nullptr); }
|
||||||
|
template <int N = Nurse, int P = Patient, typename std::enable_if<N != 0 && P != 0, int>::type = 0>
|
||||||
|
static void postcall(PyObject *, PyObject *) { }
|
||||||
|
template <int N = Nurse, int P = Patient, typename std::enable_if<N == 0 || P == 0, int>::type = 0>
|
||||||
|
static void precall(PyObject *) { }
|
||||||
|
template <int N = Nurse, int P = Patient, typename std::enable_if<N == 0 || P == 0, int>::type = 0>
|
||||||
|
static void postcall(PyObject *arg, PyObject *ret) { keep_alive_impl(Nurse, Patient, arg, ret); }
|
||||||
|
};
|
||||||
|
|
||||||
NAMESPACE_END(detail)
|
NAMESPACE_END(detail)
|
||||||
|
|
||||||
template <typename... Args> detail::init<Args...> init() { return detail::init<Args...>(); };
|
template <typename... Args> detail::init<Args...> init() { return detail::init<Args...>(); };
|
||||||
|
Loading…
Reference in New Issue
Block a user