mirror of
https://github.com/pybind/pybind11.git
synced 2024-11-25 06:35:12 +00:00
Improve custom holder support (#607)
* Abstract away some holder functionality (resolve #585) Custom holder types which don't have `.get()` can select the correct function to call by specializing `holder_traits`. * Add support for move-only holders (fix #605)
This commit is contained in:
parent
f7f5bc8e37
commit
ec009a7ca2
@ -149,6 +149,27 @@ situation where ``true`` should be passed is when the ``T`` instances use
|
|||||||
|
|
||||||
Please take a look at the :ref:`macro_notes` before using this feature.
|
Please take a look at the :ref:`macro_notes` before using this feature.
|
||||||
|
|
||||||
|
By default, pybind11 assumes that your custom smart pointer has a standard
|
||||||
|
interface, i.e. provides a ``.get()`` member function to access the underlying
|
||||||
|
raw pointer. If this is not the case, pybind11's ``holder_helper`` must be
|
||||||
|
specialized:
|
||||||
|
|
||||||
|
.. code-block:: cpp
|
||||||
|
|
||||||
|
// Always needed for custom holder types
|
||||||
|
PYBIND11_DECLARE_HOLDER_TYPE(T, SmartPtr<T>);
|
||||||
|
|
||||||
|
// Only needed if the type's `.get()` goes by another name
|
||||||
|
namespace pybind11 { namespace detail {
|
||||||
|
template <typename T>
|
||||||
|
struct holder_helper<SmartPtr<T>> { // <-- specialization
|
||||||
|
static const T *get(const SmartPtr<T> &p) { return p.getPointer(); }
|
||||||
|
};
|
||||||
|
}}
|
||||||
|
|
||||||
|
The above specialization informs pybind11 that the custom ``SmartPtr`` class
|
||||||
|
provides ``.get()`` functionality via ``.getPointer()``.
|
||||||
|
|
||||||
.. seealso::
|
.. seealso::
|
||||||
|
|
||||||
The file :file:`tests/test_smart_ptr.cpp` contains a complete example
|
The file :file:`tests/test_smart_ptr.cpp` contains a complete example
|
||||||
|
@ -402,6 +402,13 @@ public:
|
|||||||
make_copy_constructor(src), make_move_constructor(src));
|
make_copy_constructor(src), make_move_constructor(src));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static handle cast_holder(const itype *src, const void *holder) {
|
||||||
|
return type_caster_generic::cast(
|
||||||
|
src, return_value_policy::take_ownership, {},
|
||||||
|
src ? &typeid(*src) : nullptr, &typeid(type),
|
||||||
|
nullptr, nullptr, holder);
|
||||||
|
}
|
||||||
|
|
||||||
template <typename T> using cast_op_type = pybind11::detail::cast_op_type<T>;
|
template <typename T> using cast_op_type = pybind11::detail::cast_op_type<T>;
|
||||||
|
|
||||||
operator itype*() { return (type *) value; }
|
operator itype*() { return (type *) value; }
|
||||||
@ -636,17 +643,6 @@ protected:
|
|||||||
bool success = false;
|
bool success = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename type, typename deleter> class type_caster<std::unique_ptr<type, deleter>> {
|
|
||||||
public:
|
|
||||||
static handle cast(std::unique_ptr<type, deleter> &&src, return_value_policy policy, handle parent) {
|
|
||||||
handle result = type_caster_base<type>::cast(src.get(), policy, parent);
|
|
||||||
if (result)
|
|
||||||
src.release();
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
static PYBIND11_DESCR name() { return type_caster_base<type>::name(); }
|
|
||||||
};
|
|
||||||
|
|
||||||
template <> class type_caster<std::wstring> {
|
template <> class type_caster<std::wstring> {
|
||||||
public:
|
public:
|
||||||
bool load(handle src, bool) {
|
bool load(handle src, bool) {
|
||||||
@ -837,8 +833,16 @@ protected:
|
|||||||
std::tuple<make_caster<Tuple>...> value;
|
std::tuple<make_caster<Tuple>...> value;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// Helper class which abstracts away certain actions. Users can provide specializations for
|
||||||
|
/// custom holders, but it's only necessary if the type has a non-standard interface.
|
||||||
|
template <typename T>
|
||||||
|
struct holder_helper {
|
||||||
|
static auto get(const T &p) -> decltype(p.get()) { return p.get(); }
|
||||||
|
};
|
||||||
|
|
||||||
/// Type caster for holder types like std::shared_ptr, etc.
|
/// Type caster for holder types like std::shared_ptr, etc.
|
||||||
template <typename type, typename holder_type> class type_caster_holder : public type_caster_base<type> {
|
template <typename type, typename holder_type>
|
||||||
|
struct copyable_holder_caster : public type_caster_base<type> {
|
||||||
public:
|
public:
|
||||||
using base = type_caster_base<type>;
|
using base = type_caster_base<type>;
|
||||||
using base::base;
|
using base::base;
|
||||||
@ -917,7 +921,7 @@ public:
|
|||||||
template <typename T = holder_type, detail::enable_if_t<std::is_constructible<T, const T &, type*>::value, int> = 0>
|
template <typename T = holder_type, detail::enable_if_t<std::is_constructible<T, const T &, type*>::value, int> = 0>
|
||||||
bool try_implicit_casts(handle src, bool convert) {
|
bool try_implicit_casts(handle src, bool convert) {
|
||||||
for (auto &cast : typeinfo->implicit_casts) {
|
for (auto &cast : typeinfo->implicit_casts) {
|
||||||
type_caster_holder sub_caster(*cast.first);
|
copyable_holder_caster sub_caster(*cast.first);
|
||||||
if (sub_caster.load(src, convert)) {
|
if (sub_caster.load(src, convert)) {
|
||||||
value = cast.second(sub_caster.value);
|
value = cast.second(sub_caster.value);
|
||||||
holder = holder_type(sub_caster.holder, (type *) value);
|
holder = holder_type(sub_caster.holder, (type *) value);
|
||||||
@ -940,10 +944,8 @@ public:
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
static handle cast(const holder_type &src, return_value_policy, handle) {
|
static handle cast(const holder_type &src, return_value_policy, handle) {
|
||||||
return type_caster_generic::cast(
|
const auto *ptr = holder_helper<holder_type>::get(src);
|
||||||
src.get(), return_value_policy::take_ownership, handle(),
|
return type_caster_base<type>::cast_holder(ptr, &src);
|
||||||
src.get() ? &typeid(*src.get()) : nullptr, &typeid(type),
|
|
||||||
nullptr, nullptr, &src);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
@ -952,7 +954,25 @@ protected:
|
|||||||
|
|
||||||
/// Specialize for the common std::shared_ptr, so users don't need to
|
/// Specialize for the common std::shared_ptr, so users don't need to
|
||||||
template <typename T>
|
template <typename T>
|
||||||
class type_caster<std::shared_ptr<T>> : public type_caster_holder<T, std::shared_ptr<T>> { };
|
class type_caster<std::shared_ptr<T>> : public copyable_holder_caster<T, std::shared_ptr<T>> { };
|
||||||
|
|
||||||
|
template <typename type, typename holder_type>
|
||||||
|
struct move_only_holder_caster {
|
||||||
|
static handle cast(holder_type &&src, return_value_policy, handle) {
|
||||||
|
auto *ptr = holder_helper<holder_type>::get(src);
|
||||||
|
return type_caster_base<type>::cast_holder(ptr, &src);
|
||||||
|
}
|
||||||
|
static PYBIND11_DESCR name() { return type_caster_base<type>::name(); }
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename type, typename deleter>
|
||||||
|
class type_caster<std::unique_ptr<type, deleter>>
|
||||||
|
: public move_only_holder_caster<type, std::unique_ptr<type, deleter>> { };
|
||||||
|
|
||||||
|
template <typename type, typename holder_type>
|
||||||
|
using type_caster_holder = conditional_t<std::is_copy_constructible<holder_type>::value,
|
||||||
|
copyable_holder_caster<type, holder_type>,
|
||||||
|
move_only_holder_caster<type, holder_type>>;
|
||||||
|
|
||||||
template <typename T, bool Value = false> struct always_construct_holder { static constexpr bool value = Value; };
|
template <typename T, bool Value = false> struct always_construct_holder { static constexpr bool value = Value; };
|
||||||
|
|
||||||
|
@ -1212,24 +1212,22 @@ private:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Initialize holder object, variant 2: try to construct from existing holder object, if possible
|
static void init_holder_from_existing(instance_type *inst, const holder_type *holder_ptr,
|
||||||
template <typename T = holder_type,
|
std::true_type /*is_copy_constructible*/) {
|
||||||
detail::enable_if_t<std::is_copy_constructible<T>::value, int> = 0>
|
new (&inst->holder) holder_type(*holder_ptr);
|
||||||
static void init_holder_helper(instance_type *inst, const holder_type *holder_ptr, const void * /* dummy */) {
|
|
||||||
if (holder_ptr) {
|
|
||||||
new (&inst->holder) holder_type(*holder_ptr);
|
|
||||||
inst->holder_constructed = true;
|
|
||||||
} else if (inst->owned || detail::always_construct_holder<holder_type>::value) {
|
|
||||||
new (&inst->holder) holder_type(inst->value);
|
|
||||||
inst->holder_constructed = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Initialize holder object, variant 3: holder is not copy constructible (e.g. unique_ptr), always initialize from raw pointer
|
static void init_holder_from_existing(instance_type *inst, const holder_type *holder_ptr,
|
||||||
template <typename T = holder_type,
|
std::false_type /*is_copy_constructible*/) {
|
||||||
detail::enable_if_t<!std::is_copy_constructible<T>::value, int> = 0>
|
new (&inst->holder) holder_type(std::move(*const_cast<holder_type *>(holder_ptr)));
|
||||||
static void init_holder_helper(instance_type *inst, const holder_type * /* unused */, const void * /* dummy */) {
|
}
|
||||||
if (inst->owned || detail::always_construct_holder<holder_type>::value) {
|
|
||||||
|
/// Initialize holder object, variant 2: try to construct from existing holder object, if possible
|
||||||
|
static void init_holder_helper(instance_type *inst, const holder_type *holder_ptr, const void * /* dummy */) {
|
||||||
|
if (holder_ptr) {
|
||||||
|
init_holder_from_existing(inst, holder_ptr, std::is_copy_constructible<holder_type>());
|
||||||
|
inst->holder_constructed = true;
|
||||||
|
} else if (inst->owned || detail::always_construct_holder<holder_type>::value) {
|
||||||
new (&inst->holder) holder_type(inst->value);
|
new (&inst->holder) holder_type(inst->value);
|
||||||
inst->holder_constructed = true;
|
inst->holder_constructed = true;
|
||||||
}
|
}
|
||||||
|
@ -164,10 +164,10 @@ public:
|
|||||||
operator T* () { return m_ptr; }
|
operator T* () { return m_ptr; }
|
||||||
|
|
||||||
/// Return a const pointer to the referenced object
|
/// Return a const pointer to the referenced object
|
||||||
T* get() { return m_ptr; }
|
T* get_ptr() { return m_ptr; }
|
||||||
|
|
||||||
/// Return a pointer to the referenced object
|
/// Return a pointer to the referenced object
|
||||||
const T* get() const { return m_ptr; }
|
const T* get_ptr() const { return m_ptr; }
|
||||||
private:
|
private:
|
||||||
T *m_ptr;
|
T *m_ptr;
|
||||||
};
|
};
|
||||||
|
@ -90,6 +90,14 @@ PYBIND11_DECLARE_HOLDER_TYPE(T, ref<T>, true);
|
|||||||
PYBIND11_DECLARE_HOLDER_TYPE(T, std::shared_ptr<T>); // Not required any more for std::shared_ptr,
|
PYBIND11_DECLARE_HOLDER_TYPE(T, std::shared_ptr<T>); // Not required any more for std::shared_ptr,
|
||||||
// but it should compile without error
|
// but it should compile without error
|
||||||
|
|
||||||
|
// Make pybind11 aware of the non-standard getter member function
|
||||||
|
namespace pybind11 { namespace detail {
|
||||||
|
template <typename T>
|
||||||
|
struct holder_helper<ref<T>> {
|
||||||
|
static const T *get(const ref<T> &p) { return p.get_ptr(); }
|
||||||
|
};
|
||||||
|
}}
|
||||||
|
|
||||||
Object *make_object_1() { return new MyObject1(1); }
|
Object *make_object_1() { return new MyObject1(1); }
|
||||||
ref<Object> make_object_2() { return new MyObject1(2); }
|
ref<Object> make_object_2() { return new MyObject1(2); }
|
||||||
|
|
||||||
@ -206,6 +214,18 @@ struct SharedFromThisRef {
|
|||||||
std::shared_ptr<B> shared = std::make_shared<B>();
|
std::shared_ptr<B> shared = std::make_shared<B>();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
class CustomUniquePtr {
|
||||||
|
std::unique_ptr<T> impl;
|
||||||
|
|
||||||
|
public:
|
||||||
|
CustomUniquePtr(T* p) : impl(p) { }
|
||||||
|
T* get() const { return impl.get(); }
|
||||||
|
T* release_ptr() { return impl.release(); }
|
||||||
|
};
|
||||||
|
|
||||||
|
PYBIND11_DECLARE_HOLDER_TYPE(T, CustomUniquePtr<T>);
|
||||||
|
|
||||||
test_initializer smart_ptr_and_references([](py::module &pm) {
|
test_initializer smart_ptr_and_references([](py::module &pm) {
|
||||||
auto m = pm.def_submodule("smart_ptr");
|
auto m = pm.def_submodule("smart_ptr");
|
||||||
|
|
||||||
@ -237,4 +257,12 @@ test_initializer smart_ptr_and_references([](py::module &pm) {
|
|||||||
py::return_value_policy::copy)
|
py::return_value_policy::copy)
|
||||||
.def("set_ref", [](SharedFromThisRef &, const B &) { return true; })
|
.def("set_ref", [](SharedFromThisRef &, const B &) { return true; })
|
||||||
.def("set_holder", [](SharedFromThisRef &, std::shared_ptr<B>) { return true; });
|
.def("set_holder", [](SharedFromThisRef &, std::shared_ptr<B>) { return true; });
|
||||||
|
|
||||||
|
struct C {
|
||||||
|
C() { print_created(this); }
|
||||||
|
~C() { print_destroyed(this); }
|
||||||
|
};
|
||||||
|
|
||||||
|
py::class_<C, CustomUniquePtr<C>>(m, "TypeWithMoveOnlyHolder")
|
||||||
|
.def_static("make", []() { return CustomUniquePtr<C>(new C); });
|
||||||
});
|
});
|
||||||
|
@ -201,3 +201,13 @@ def test_shared_ptr_from_this_and_references():
|
|||||||
|
|
||||||
del ref, bad_wp, copy, holder_ref, holder_copy, s
|
del ref, bad_wp, copy, holder_ref, holder_copy, s
|
||||||
assert stats.alive() == 0
|
assert stats.alive() == 0
|
||||||
|
|
||||||
|
|
||||||
|
def test_move_only_holder():
|
||||||
|
from pybind11_tests.smart_ptr import TypeWithMoveOnlyHolder
|
||||||
|
|
||||||
|
a = TypeWithMoveOnlyHolder.make()
|
||||||
|
stats = ConstructorStats.get(TypeWithMoveOnlyHolder)
|
||||||
|
assert stats.alive() == 1
|
||||||
|
del a
|
||||||
|
assert stats.alive() == 0
|
||||||
|
Loading…
Reference in New Issue
Block a user