Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve custom holder support #607

Merged
merged 2 commits into from
Jan 31, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions docs/advanced/smart_ptrs.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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.

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

The file :file:`tests/test_smart_ptr.cpp` contains a complete example
Expand Down
56 changes: 38 additions & 18 deletions include/pybind11/cast.h
Original file line number Diff line number Diff line change
Expand Up @@ -400,6 +400,13 @@ template <typename type> class type_caster_base : public type_caster_generic {
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>;

operator itype*() { return (type *) value; }
Expand Down Expand Up @@ -634,17 +641,6 @@ template <> class type_caster<std::string> {
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> {
public:
bool load(handle src, bool) {
Expand Down Expand Up @@ -835,8 +831,16 @@ template <typename... Tuple> class type_caster<std::tuple<Tuple...>> {
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.
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:
using base = type_caster_base<type>;
using base::base;
Expand Down Expand Up @@ -915,7 +919,7 @@ template <typename type, typename holder_type> class type_caster_holder : public
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) {
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)) {
value = cast.second(sub_caster.value);
holder = holder_type(sub_caster.holder, (type *) value);
Expand All @@ -938,10 +942,8 @@ template <typename type, typename holder_type> class type_caster_holder : public
#endif

static handle cast(const holder_type &src, return_value_policy, handle) {
return type_caster_generic::cast(
src.get(), return_value_policy::take_ownership, handle(),
src.get() ? &typeid(*src.get()) : nullptr, &typeid(type),
nullptr, nullptr, &src);
const auto *ptr = holder_helper<holder_type>::get(src);
return type_caster_base<type>::cast_holder(ptr, &src);
}

protected:
Expand All @@ -950,7 +952,25 @@ template <typename type, typename holder_type> class type_caster_holder : public

/// Specialize for the common std::shared_ptr, so users don't need to
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; };

Expand Down
24 changes: 11 additions & 13 deletions include/pybind11/pybind11.h
Original file line number Diff line number Diff line change
Expand Up @@ -1193,29 +1193,27 @@ class class_ : public detail::generic_type {
}
}

static void init_holder_from_existing(instance_type *inst, const holder_type *holder_ptr,
std::true_type /*is_copy_constructible*/) {
new (&inst->holder) holder_type(*holder_ptr);
}

static void init_holder_from_existing(instance_type *inst, const holder_type *holder_ptr,
std::false_type /*is_copy_constructible*/) {
new (&inst->holder) holder_type(std::move(*const_cast<holder_type *>(holder_ptr)));
}
Copy link
Contributor

@pschella pschella Jan 18, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps add a two argument version to dispatch?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure I follow. Two argument version of what?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mean adding static void init_holder_from_existing(instance_type *inst, const holder_type *holder_ptr) to dispatch between the two options.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think another layer of abstraction is needed here since this is all implementation detail and the intent should be clear from init_holder_helper right below.


/// Initialize holder object, variant 2: try to construct from existing holder object, if possible
template <typename T = holder_type,
detail::enable_if_t<std::is_copy_constructible<T>::value, int> = 0>
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);
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);
inst->holder_constructed = true;
}
}

/// Initialize holder object, variant 3: holder is not copy constructible (e.g. unique_ptr), always initialize from raw pointer
template <typename T = holder_type,
detail::enable_if_t<!std::is_copy_constructible<T>::value, int> = 0>
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) {
new (&inst->holder) holder_type(inst->value);
inst->holder_constructed = true;
}
}

/// Initialize holder object of an instance, possibly given a pointer to an existing holder
static void init_holder(PyObject *inst_, const void *holder_ptr) {
auto inst = (instance_type *) inst_;
Expand Down
4 changes: 2 additions & 2 deletions tests/object.h
Original file line number Diff line number Diff line change
Expand Up @@ -164,10 +164,10 @@ template <typename T> class ref {
operator T* () { return m_ptr; }

/// 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
const T* get() const { return m_ptr; }
const T* get_ptr() const { return m_ptr; }
private:
T *m_ptr;
};
Expand Down
28 changes: 28 additions & 0 deletions tests/test_smart_ptr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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,
// 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); }
ref<Object> make_object_2() { return new MyObject1(2); }

Expand Down Expand Up @@ -206,6 +214,18 @@ struct SharedFromThisRef {
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) {
auto m = pm.def_submodule("smart_ptr");

Expand Down Expand Up @@ -237,4 +257,12 @@ test_initializer smart_ptr_and_references([](py::module &pm) {
py::return_value_policy::copy)
.def("set_ref", [](SharedFromThisRef &, const 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); });
});
10 changes: 10 additions & 0 deletions tests/test_smart_ptr.py
Original file line number Diff line number Diff line change
Expand Up @@ -201,3 +201,13 @@ def test_shared_ptr_from_this_and_references():

del ref, bad_wp, copy, holder_ref, holder_copy, s
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