Skip to content

Commit ec009a7

Browse files
dean0x7dwjakob
authored andcommitted
Improve custom holder support (pybind#607)
* Abstract away some holder functionality (resolve pybind#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 pybind#605)
1 parent f7f5bc8 commit ec009a7

File tree

6 files changed

+110
-33
lines changed

6 files changed

+110
-33
lines changed

docs/advanced/smart_ptrs.rst

+21
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,27 @@ situation where ``true`` should be passed is when the ``T`` instances use
149149

150150
Please take a look at the :ref:`macro_notes` before using this feature.
151151

152+
By default, pybind11 assumes that your custom smart pointer has a standard
153+
interface, i.e. provides a ``.get()`` member function to access the underlying
154+
raw pointer. If this is not the case, pybind11's ``holder_helper`` must be
155+
specialized:
156+
157+
.. code-block:: cpp
158+
159+
// Always needed for custom holder types
160+
PYBIND11_DECLARE_HOLDER_TYPE(T, SmartPtr<T>);
161+
162+
// Only needed if the type's `.get()` goes by another name
163+
namespace pybind11 { namespace detail {
164+
template <typename T>
165+
struct holder_helper<SmartPtr<T>> { // <-- specialization
166+
static const T *get(const SmartPtr<T> &p) { return p.getPointer(); }
167+
};
168+
}}
169+
170+
The above specialization informs pybind11 that the custom ``SmartPtr`` class
171+
provides ``.get()`` functionality via ``.getPointer()``.
172+
152173
.. seealso::
153174

154175
The file :file:`tests/test_smart_ptr.cpp` contains a complete example

include/pybind11/cast.h

+38-18
Original file line numberDiff line numberDiff line change
@@ -402,6 +402,13 @@ template <typename type> class type_caster_base : public type_caster_generic {
402402
make_copy_constructor(src), make_move_constructor(src));
403403
}
404404

405+
static handle cast_holder(const itype *src, const void *holder) {
406+
return type_caster_generic::cast(
407+
src, return_value_policy::take_ownership, {},
408+
src ? &typeid(*src) : nullptr, &typeid(type),
409+
nullptr, nullptr, holder);
410+
}
411+
405412
template <typename T> using cast_op_type = pybind11::detail::cast_op_type<T>;
406413

407414
operator itype*() { return (type *) value; }
@@ -636,17 +643,6 @@ template <> class type_caster<std::string> {
636643
bool success = false;
637644
};
638645

639-
template <typename type, typename deleter> class type_caster<std::unique_ptr<type, deleter>> {
640-
public:
641-
static handle cast(std::unique_ptr<type, deleter> &&src, return_value_policy policy, handle parent) {
642-
handle result = type_caster_base<type>::cast(src.get(), policy, parent);
643-
if (result)
644-
src.release();
645-
return result;
646-
}
647-
static PYBIND11_DESCR name() { return type_caster_base<type>::name(); }
648-
};
649-
650646
template <> class type_caster<std::wstring> {
651647
public:
652648
bool load(handle src, bool) {
@@ -837,8 +833,16 @@ template <typename... Tuple> class type_caster<std::tuple<Tuple...>> {
837833
std::tuple<make_caster<Tuple>...> value;
838834
};
839835

836+
/// Helper class which abstracts away certain actions. Users can provide specializations for
837+
/// custom holders, but it's only necessary if the type has a non-standard interface.
838+
template <typename T>
839+
struct holder_helper {
840+
static auto get(const T &p) -> decltype(p.get()) { return p.get(); }
841+
};
842+
840843
/// Type caster for holder types like std::shared_ptr, etc.
841-
template <typename type, typename holder_type> class type_caster_holder : public type_caster_base<type> {
844+
template <typename type, typename holder_type>
845+
struct copyable_holder_caster : public type_caster_base<type> {
842846
public:
843847
using base = type_caster_base<type>;
844848
using base::base;
@@ -917,7 +921,7 @@ template <typename type, typename holder_type> class type_caster_holder : public
917921
template <typename T = holder_type, detail::enable_if_t<std::is_constructible<T, const T &, type*>::value, int> = 0>
918922
bool try_implicit_casts(handle src, bool convert) {
919923
for (auto &cast : typeinfo->implicit_casts) {
920-
type_caster_holder sub_caster(*cast.first);
924+
copyable_holder_caster sub_caster(*cast.first);
921925
if (sub_caster.load(src, convert)) {
922926
value = cast.second(sub_caster.value);
923927
holder = holder_type(sub_caster.holder, (type *) value);
@@ -940,10 +944,8 @@ template <typename type, typename holder_type> class type_caster_holder : public
940944
#endif
941945

942946
static handle cast(const holder_type &src, return_value_policy, handle) {
943-
return type_caster_generic::cast(
944-
src.get(), return_value_policy::take_ownership, handle(),
945-
src.get() ? &typeid(*src.get()) : nullptr, &typeid(type),
946-
nullptr, nullptr, &src);
947+
const auto *ptr = holder_helper<holder_type>::get(src);
948+
return type_caster_base<type>::cast_holder(ptr, &src);
947949
}
948950

949951
protected:
@@ -952,7 +954,25 @@ template <typename type, typename holder_type> class type_caster_holder : public
952954

953955
/// Specialize for the common std::shared_ptr, so users don't need to
954956
template <typename T>
955-
class type_caster<std::shared_ptr<T>> : public type_caster_holder<T, std::shared_ptr<T>> { };
957+
class type_caster<std::shared_ptr<T>> : public copyable_holder_caster<T, std::shared_ptr<T>> { };
958+
959+
template <typename type, typename holder_type>
960+
struct move_only_holder_caster {
961+
static handle cast(holder_type &&src, return_value_policy, handle) {
962+
auto *ptr = holder_helper<holder_type>::get(src);
963+
return type_caster_base<type>::cast_holder(ptr, &src);
964+
}
965+
static PYBIND11_DESCR name() { return type_caster_base<type>::name(); }
966+
};
967+
968+
template <typename type, typename deleter>
969+
class type_caster<std::unique_ptr<type, deleter>>
970+
: public move_only_holder_caster<type, std::unique_ptr<type, deleter>> { };
971+
972+
template <typename type, typename holder_type>
973+
using type_caster_holder = conditional_t<std::is_copy_constructible<holder_type>::value,
974+
copyable_holder_caster<type, holder_type>,
975+
move_only_holder_caster<type, holder_type>>;
956976

957977
template <typename T, bool Value = false> struct always_construct_holder { static constexpr bool value = Value; };
958978

include/pybind11/pybind11.h

+11-13
Original file line numberDiff line numberDiff line change
@@ -1212,29 +1212,27 @@ class class_ : public detail::generic_type {
12121212
}
12131213
}
12141214

1215+
static void init_holder_from_existing(instance_type *inst, const holder_type *holder_ptr,
1216+
std::true_type /*is_copy_constructible*/) {
1217+
new (&inst->holder) holder_type(*holder_ptr);
1218+
}
1219+
1220+
static void init_holder_from_existing(instance_type *inst, const holder_type *holder_ptr,
1221+
std::false_type /*is_copy_constructible*/) {
1222+
new (&inst->holder) holder_type(std::move(*const_cast<holder_type *>(holder_ptr)));
1223+
}
1224+
12151225
/// Initialize holder object, variant 2: try to construct from existing holder object, if possible
1216-
template <typename T = holder_type,
1217-
detail::enable_if_t<std::is_copy_constructible<T>::value, int> = 0>
12181226
static void init_holder_helper(instance_type *inst, const holder_type *holder_ptr, const void * /* dummy */) {
12191227
if (holder_ptr) {
1220-
new (&inst->holder) holder_type(*holder_ptr);
1228+
init_holder_from_existing(inst, holder_ptr, std::is_copy_constructible<holder_type>());
12211229
inst->holder_constructed = true;
12221230
} else if (inst->owned || detail::always_construct_holder<holder_type>::value) {
12231231
new (&inst->holder) holder_type(inst->value);
12241232
inst->holder_constructed = true;
12251233
}
12261234
}
12271235

1228-
/// Initialize holder object, variant 3: holder is not copy constructible (e.g. unique_ptr), always initialize from raw pointer
1229-
template <typename T = holder_type,
1230-
detail::enable_if_t<!std::is_copy_constructible<T>::value, int> = 0>
1231-
static void init_holder_helper(instance_type *inst, const holder_type * /* unused */, const void * /* dummy */) {
1232-
if (inst->owned || detail::always_construct_holder<holder_type>::value) {
1233-
new (&inst->holder) holder_type(inst->value);
1234-
inst->holder_constructed = true;
1235-
}
1236-
}
1237-
12381236
/// Initialize holder object of an instance, possibly given a pointer to an existing holder
12391237
static void init_holder(PyObject *inst_, const void *holder_ptr) {
12401238
auto inst = (instance_type *) inst_;

tests/object.h

+2-2
Original file line numberDiff line numberDiff line change
@@ -164,10 +164,10 @@ template <typename T> class ref {
164164
operator T* () { return m_ptr; }
165165

166166
/// Return a const pointer to the referenced object
167-
T* get() { return m_ptr; }
167+
T* get_ptr() { return m_ptr; }
168168

169169
/// Return a pointer to the referenced object
170-
const T* get() const { return m_ptr; }
170+
const T* get_ptr() const { return m_ptr; }
171171
private:
172172
T *m_ptr;
173173
};

tests/test_smart_ptr.cpp

+28
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,14 @@ PYBIND11_DECLARE_HOLDER_TYPE(T, ref<T>, true);
9090
PYBIND11_DECLARE_HOLDER_TYPE(T, std::shared_ptr<T>); // Not required any more for std::shared_ptr,
9191
// but it should compile without error
9292

93+
// Make pybind11 aware of the non-standard getter member function
94+
namespace pybind11 { namespace detail {
95+
template <typename T>
96+
struct holder_helper<ref<T>> {
97+
static const T *get(const ref<T> &p) { return p.get_ptr(); }
98+
};
99+
}}
100+
93101
Object *make_object_1() { return new MyObject1(1); }
94102
ref<Object> make_object_2() { return new MyObject1(2); }
95103

@@ -206,6 +214,18 @@ struct SharedFromThisRef {
206214
std::shared_ptr<B> shared = std::make_shared<B>();
207215
};
208216

217+
template <typename T>
218+
class CustomUniquePtr {
219+
std::unique_ptr<T> impl;
220+
221+
public:
222+
CustomUniquePtr(T* p) : impl(p) { }
223+
T* get() const { return impl.get(); }
224+
T* release_ptr() { return impl.release(); }
225+
};
226+
227+
PYBIND11_DECLARE_HOLDER_TYPE(T, CustomUniquePtr<T>);
228+
209229
test_initializer smart_ptr_and_references([](py::module &pm) {
210230
auto m = pm.def_submodule("smart_ptr");
211231

@@ -237,4 +257,12 @@ test_initializer smart_ptr_and_references([](py::module &pm) {
237257
py::return_value_policy::copy)
238258
.def("set_ref", [](SharedFromThisRef &, const B &) { return true; })
239259
.def("set_holder", [](SharedFromThisRef &, std::shared_ptr<B>) { return true; });
260+
261+
struct C {
262+
C() { print_created(this); }
263+
~C() { print_destroyed(this); }
264+
};
265+
266+
py::class_<C, CustomUniquePtr<C>>(m, "TypeWithMoveOnlyHolder")
267+
.def_static("make", []() { return CustomUniquePtr<C>(new C); });
240268
});

tests/test_smart_ptr.py

+10
Original file line numberDiff line numberDiff line change
@@ -201,3 +201,13 @@ def test_shared_ptr_from_this_and_references():
201201

202202
del ref, bad_wp, copy, holder_ref, holder_copy, s
203203
assert stats.alive() == 0
204+
205+
206+
def test_move_only_holder():
207+
from pybind11_tests.smart_ptr import TypeWithMoveOnlyHolder
208+
209+
a = TypeWithMoveOnlyHolder.make()
210+
stats = ConstructorStats.get(TypeWithMoveOnlyHolder)
211+
assert stats.alive() == 1
212+
del a
213+
assert stats.alive() == 0

0 commit comments

Comments
 (0)