Skip to content

Commit ab90ec6

Browse files
dean0x7dwjakob
authored andcommitted
Allow references to objects held by smart pointers (#533)
1 parent 8c85a85 commit ab90ec6

File tree

5 files changed

+159
-19
lines changed

5 files changed

+159
-19
lines changed

docs/advanced/smart_ptrs.rst

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,6 @@ code?
5353

5454
.. code-block:: cpp
5555
56-
PYBIND11_DECLARE_HOLDER_TYPE(T, std::shared_ptr<T>);
57-
5856
class Child { };
5957
6058
class Parent {

include/pybind11/cast.h

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -860,20 +860,12 @@ template <typename type, typename holder_type> class type_caster_holder : public
860860

861861
if (typeinfo->simple_type) { /* Case 1: no multiple inheritance etc. involved */
862862
/* Check if we can safely perform a reinterpret-style cast */
863-
if (PyType_IsSubtype(tobj, typeinfo->type)) {
864-
auto inst = (instance<type, holder_type> *) src.ptr();
865-
value = (void *) inst->value;
866-
holder = inst->holder;
867-
return true;
868-
}
863+
if (PyType_IsSubtype(tobj, typeinfo->type))
864+
return load_value_and_holder(src);
869865
} else { /* Case 2: multiple inheritance */
870866
/* Check if we can safely perform a reinterpret-style cast */
871-
if (tobj == typeinfo->type) {
872-
auto inst = (instance<type, holder_type> *) src.ptr();
873-
value = (void *) inst->value;
874-
holder = inst->holder;
875-
return true;
876-
}
867+
if (tobj == typeinfo->type)
868+
return load_value_and_holder(src);
877869

878870
/* If this is a python class, also check the parents recursively */
879871
auto const &type_dict = get_internals().registered_types_py;
@@ -902,6 +894,22 @@ template <typename type, typename holder_type> class type_caster_holder : public
902894
return false;
903895
}
904896

897+
bool load_value_and_holder(handle src) {
898+
auto inst = (instance<type, holder_type> *) src.ptr();
899+
value = (void *) inst->value;
900+
if (inst->holder_constructed) {
901+
holder = inst->holder;
902+
return true;
903+
} else {
904+
throw cast_error("Unable to cast from non-held to held instance (T& to Holder<T>) "
905+
#if defined(NDEBUG)
906+
"(compile in debug mode for type information)");
907+
#else
908+
"of type '" + type_id<holder_type>() + "''");
909+
#endif
910+
}
911+
}
912+
905913
template <typename T = holder_type, detail::enable_if_t<!std::is_constructible<T, const T &, type*>::value, int> = 0>
906914
bool try_implicit_casts(handle, bool) { return false; }
907915

include/pybind11/pybind11.h

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1138,21 +1138,26 @@ class class_ : public detail::generic_type {
11381138
static void init_holder_helper(instance_type *inst, const holder_type * /* unused */, const std::enable_shared_from_this<T> * /* dummy */) {
11391139
try {
11401140
new (&inst->holder) holder_type(std::static_pointer_cast<typename holder_type::element_type>(inst->value->shared_from_this()));
1141+
inst->holder_constructed = true;
11411142
} catch (const std::bad_weak_ptr &) {
1142-
new (&inst->holder) holder_type(inst->value);
1143+
if (inst->owned) {
1144+
new (&inst->holder) holder_type(inst->value);
1145+
inst->holder_constructed = true;
1146+
}
11431147
}
1144-
inst->holder_constructed = true;
11451148
}
11461149

11471150
/// Initialize holder object, variant 2: try to construct from existing holder object, if possible
11481151
template <typename T = holder_type,
11491152
detail::enable_if_t<std::is_copy_constructible<T>::value, int> = 0>
11501153
static void init_holder_helper(instance_type *inst, const holder_type *holder_ptr, const void * /* dummy */) {
1151-
if (holder_ptr)
1154+
if (holder_ptr) {
11521155
new (&inst->holder) holder_type(*holder_ptr);
1153-
else
1156+
inst->holder_constructed = true;
1157+
} else if (inst->owned) {
11541158
new (&inst->holder) holder_type(inst->value);
1155-
inst->holder_constructed = true;
1159+
inst->holder_constructed = true;
1160+
}
11561161
}
11571162

11581163
/// Initialize holder object, variant 3: holder is not copy constructible (e.g. unique_ptr), always initialize from raw pointer

tests/test_smart_ptr.cpp

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,3 +165,60 @@ test_initializer smart_ptr([](py::module &m) {
165165
// Expose constructor stats for the ref type
166166
m.def("cstats_ref", &ConstructorStats::get<ref_tag>);
167167
});
168+
169+
struct SharedPtrRef {
170+
struct A {
171+
A() { print_created(this); }
172+
A(const A &) { print_copy_created(this); }
173+
A(A &&) { print_move_created(this); }
174+
~A() { print_destroyed(this); }
175+
};
176+
177+
A value = {};
178+
std::shared_ptr<A> shared = std::make_shared<A>();
179+
};
180+
181+
struct SharedFromThisRef {
182+
struct B : std::enable_shared_from_this<B> {
183+
B() { print_created(this); }
184+
B(const B &) : std::enable_shared_from_this<B>() { print_copy_created(this); }
185+
B(B &&) : std::enable_shared_from_this<B>() { print_move_created(this); }
186+
~B() { print_destroyed(this); }
187+
};
188+
189+
B value = {};
190+
std::shared_ptr<B> shared = std::make_shared<B>();
191+
};
192+
193+
test_initializer smart_ptr_and_references([](py::module &pm) {
194+
auto m = pm.def_submodule("smart_ptr");
195+
196+
using A = SharedPtrRef::A;
197+
py::class_<A, std::shared_ptr<A>>(m, "A");
198+
199+
py::class_<SharedPtrRef>(m, "SharedPtrRef")
200+
.def(py::init<>())
201+
.def_readonly("ref", &SharedPtrRef::value)
202+
.def_property_readonly("copy", [](const SharedPtrRef &s) { return s.value; },
203+
py::return_value_policy::copy)
204+
.def_readonly("holder_ref", &SharedPtrRef::shared)
205+
.def_property_readonly("holder_copy", [](const SharedPtrRef &s) { return s.shared; },
206+
py::return_value_policy::copy)
207+
.def("set_ref", [](SharedPtrRef &, const A &) { return true; })
208+
.def("set_holder", [](SharedPtrRef &, std::shared_ptr<A>) { return true; });
209+
210+
using B = SharedFromThisRef::B;
211+
py::class_<B, std::shared_ptr<B>>(m, "B");
212+
213+
py::class_<SharedFromThisRef>(m, "SharedFromThisRef")
214+
.def(py::init<>())
215+
.def_readonly("bad_wp", &SharedFromThisRef::value)
216+
.def_property_readonly("ref", [](const SharedFromThisRef &s) -> const B & { return *s.shared; })
217+
.def_property_readonly("copy", [](const SharedFromThisRef &s) { return s.value; },
218+
py::return_value_policy::copy)
219+
.def_readonly("holder_ref", &SharedFromThisRef::shared)
220+
.def_property_readonly("holder_copy", [](const SharedFromThisRef &s) { return s.shared; },
221+
py::return_value_policy::copy)
222+
.def("set_ref", [](SharedFromThisRef &, const B &) { return true; })
223+
.def("set_holder", [](SharedFromThisRef &, std::shared_ptr<B>) { return true; });
224+
});

tests/test_smart_ptr.py

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import pytest
12
from pybind11_tests import ConstructorStats
23

34

@@ -124,3 +125,74 @@ def test_unique_nodelete():
124125
del o
125126
cstats = ConstructorStats.get(MyObject4)
126127
assert cstats.alive() == 1 # Leak, but that's intentional
128+
129+
130+
def test_shared_ptr_and_references():
131+
from pybind11_tests.smart_ptr import SharedPtrRef, A
132+
133+
s = SharedPtrRef()
134+
stats = ConstructorStats.get(A)
135+
assert stats.alive() == 2
136+
137+
ref = s.ref # init_holder_helper(holder_ptr=false, owned=false)
138+
assert stats.alive() == 2
139+
assert s.set_ref(ref)
140+
with pytest.raises(RuntimeError) as excinfo:
141+
assert s.set_holder(ref)
142+
assert "Unable to cast from non-held to held instance" in str(excinfo.value)
143+
144+
copy = s.copy # init_holder_helper(holder_ptr=false, owned=true)
145+
assert stats.alive() == 3
146+
assert s.set_ref(copy)
147+
assert s.set_holder(copy)
148+
149+
holder_ref = s.holder_ref # init_holder_helper(holder_ptr=true, owned=false)
150+
assert stats.alive() == 3
151+
assert s.set_ref(holder_ref)
152+
assert s.set_holder(holder_ref)
153+
154+
holder_copy = s.holder_copy # init_holder_helper(holder_ptr=true, owned=true)
155+
assert stats.alive() == 3
156+
assert s.set_ref(holder_copy)
157+
assert s.set_holder(holder_copy)
158+
159+
del ref, copy, holder_ref, holder_copy, s
160+
assert stats.alive() == 0
161+
162+
163+
def test_shared_ptr_from_this_and_references():
164+
from pybind11_tests.smart_ptr import SharedFromThisRef, B
165+
166+
s = SharedFromThisRef()
167+
stats = ConstructorStats.get(B)
168+
assert stats.alive() == 2
169+
170+
ref = s.ref # init_holder_helper(holder_ptr=false, owned=false, bad_wp=false)
171+
assert stats.alive() == 2
172+
assert s.set_ref(ref)
173+
assert s.set_holder(ref) # std::enable_shared_from_this can create a holder from a reference
174+
175+
bad_wp = s.bad_wp # init_holder_helper(holder_ptr=false, owned=false, bad_wp=true)
176+
assert stats.alive() == 2
177+
assert s.set_ref(bad_wp)
178+
with pytest.raises(RuntimeError) as excinfo:
179+
assert s.set_holder(bad_wp)
180+
assert "Unable to cast from non-held to held instance" in str(excinfo.value)
181+
182+
copy = s.copy # init_holder_helper(holder_ptr=false, owned=true, bad_wp=false)
183+
assert stats.alive() == 3
184+
assert s.set_ref(copy)
185+
assert s.set_holder(copy)
186+
187+
holder_ref = s.holder_ref # init_holder_helper(holder_ptr=true, owned=false, bad_wp=false)
188+
assert stats.alive() == 3
189+
assert s.set_ref(holder_ref)
190+
assert s.set_holder(holder_ref)
191+
192+
holder_copy = s.holder_copy # init_holder_helper(holder_ptr=true, owned=true, bad_wp=false)
193+
assert stats.alive() == 3
194+
assert s.set_ref(holder_copy)
195+
assert s.set_holder(holder_copy)
196+
197+
del ref, bad_wp, copy, holder_ref, holder_copy, s
198+
assert stats.alive() == 0

0 commit comments

Comments
 (0)