Skip to content

Commit 3b06f7a

Browse files
committed
Allow references to objects held by smart pointers
1 parent 14bfe62 commit 3b06f7a

File tree

4 files changed

+114
-17
lines changed

4 files changed

+114
-17
lines changed

include/pybind11/cast.h

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

900900
if (typeinfo->simple_type) { /* Case 1: no multiple inheritance etc. involved */
901901
/* Check if we can safely perform a reinterpret-style cast */
902-
if (PyType_IsSubtype(tobj, typeinfo->type)) {
903-
auto inst = (instance<type, holder_type> *) src.ptr();
904-
value = (void *) inst->value;
905-
holder = inst->holder;
906-
return true;
907-
}
902+
if (PyType_IsSubtype(tobj, typeinfo->type))
903+
return load_value_and_holder(src);
908904
} else { /* Case 2: multiple inheritance */
909905
/* Check if we can safely perform a reinterpret-style cast */
910-
if (tobj == typeinfo->type) {
911-
auto inst = (instance<type, holder_type> *) src.ptr();
912-
value = (void *) inst->value;
913-
holder = inst->holder;
914-
return true;
915-
}
906+
if (tobj == typeinfo->type)
907+
return load_value_and_holder(src);
916908

917909
/* If this is a python class, also check the parents recursively */
918910
auto const &type_dict = get_internals().registered_types_py;
@@ -941,6 +933,17 @@ template <typename type, typename holder_type> class type_caster_holder : public
941933
return false;
942934
}
943935

936+
bool load_value_and_holder(handle src) {
937+
auto inst = (instance<type, holder_type> *) src.ptr();
938+
value = (void *) inst->value;
939+
if (inst->holder_constructed) {
940+
holder = inst->holder;
941+
return true;
942+
} else {
943+
return false;
944+
}
945+
}
946+
944947
template <typename T = holder_type, detail::enable_if_t<!std::is_constructible<T, const T &, type*>::value, int> = 0>
945948
bool try_implicit_casts(handle, bool) { return false; }
946949

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: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,3 +165,45 @@ 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 SmartPtrs {
170+
struct Shared {
171+
Shared() { print_created(this); }
172+
~Shared() { print_destroyed(this); }
173+
};
174+
175+
struct SharedFromThis : std::enable_shared_from_this<SharedFromThis> {
176+
SharedFromThis() { print_created(this); }
177+
~SharedFromThis() { print_destroyed(this); }
178+
};
179+
180+
std::shared_ptr<Shared> shared = std::make_shared<Shared>();
181+
std::shared_ptr<SharedFromThis> from_this = std::make_shared<SharedFromThis>();
182+
SharedFromThis bad_wp = {};
183+
};
184+
185+
test_initializer smart_ptr_and_references([](py::module &pm) {
186+
auto m = pm.def_submodule("smart_ptr");
187+
188+
using Shared = SmartPtrs::Shared;
189+
using SharedFromThis = SmartPtrs::SharedFromThis;
190+
py::class_<Shared, std::shared_ptr<Shared>>(m, "Shared");
191+
py::class_<SharedFromThis, std::shared_ptr<SharedFromThis>>(m, "SharedFromThis");
192+
193+
py::class_<SmartPtrs>(m, "SmartPtrs")
194+
.def(py::init<>())
195+
.def_property("shared_ref",
196+
[](const SmartPtrs &s) -> const Shared & { return *s.shared; },
197+
[](SmartPtrs &, const Shared &) { })
198+
.def_property("shared_holder",
199+
[](const SmartPtrs &) { return std::make_shared<Shared>(); },
200+
[](SmartPtrs &, std::shared_ptr<Shared>) { })
201+
.def_property("from_this_ref",
202+
[](const SmartPtrs &s) -> const SharedFromThis & { return *s.from_this; },
203+
[](SmartPtrs &, const SharedFromThis &) { })
204+
.def_property("from_this_holder",
205+
[](const SmartPtrs &) { return std::make_shared<SharedFromThis>(); },
206+
[](SmartPtrs &, std::shared_ptr<SharedFromThis>) { })
207+
.def_property_readonly("from_this_bad_wp",
208+
[](const SmartPtrs &s) -> const SharedFromThis & { return s.bad_wp; });
209+
});

tests/test_smart_ptr.py

Lines changed: 47 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,49 @@ 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_smart_ptr_and_references():
131+
from pybind11_tests.smart_ptr import SmartPtrs, Shared, SharedFromThis
132+
133+
s = SmartPtrs()
134+
shared_stats = ConstructorStats.get(Shared)
135+
from_this_stats = ConstructorStats.get(SharedFromThis)
136+
assert shared_stats.alive() == 1
137+
assert from_this_stats.alive() == 2
138+
139+
# std::shared_ptr
140+
ref = s.shared_ref
141+
s.shared_ref = ref
142+
with pytest.raises(TypeError):
143+
s.shared_holder = ref
144+
145+
holder = s.shared_holder
146+
s.shared_ref = holder
147+
s.shared_holder = holder
148+
149+
assert shared_stats.alive() == 2
150+
del ref, holder # reference is not owned
151+
assert shared_stats.alive() == 1
152+
153+
# std::enable_shared_form_this
154+
ref = s.from_this_ref
155+
s.from_this_ref = ref
156+
s.from_this_holder = ref
157+
158+
holder = s.from_this_holder
159+
s.from_this_ref = holder
160+
s.from_this_holder = holder
161+
162+
bad_wp = s.from_this_bad_wp
163+
s.from_this_ref = bad_wp
164+
with pytest.raises(TypeError):
165+
s.from_this_holder = bad_wp
166+
167+
assert from_this_stats.alive() == 3
168+
del ref, holder, bad_wp # reference and bad_weak_ptr are not owned
169+
assert from_this_stats.alive() == 2
170+
171+
del s
172+
assert shared_stats.alive() == 0
173+
assert from_this_stats.alive() == 0

0 commit comments

Comments
 (0)