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:
Dean Moldovan 2017-01-31 17:05:44 +01:00 committed by Wenzel Jakob
parent f7f5bc8e37
commit ec009a7ca2
6 changed files with 113 additions and 36 deletions

View File

@ -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

View File

@ -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; };

View File

@ -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;
} }

View File

@ -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;
}; };

View File

@ -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); });
}); });

View File

@ -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