Skip to content

Commit 2a09774

Browse files
committed
Staw man solution to pybind#1546
1 parent 48543b1 commit 2a09774

File tree

3 files changed

+47
-2
lines changed

3 files changed

+47
-2
lines changed

docs/changelog.rst

+3
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,9 @@ v2.3.0 (Not yet released)
5050
* ``pybind11/stl.h`` does not convert strings to ``vector<string>`` anymore.
5151
`#1258 <https://github.com/pybind/pybind11/issues/1258>`_.
5252

53+
* fixed shared_ptrs to Python-derived instances outliving the associated Python object
54+
`#1546 <https://github.com/pybind/pybind11/issues/1546>`_.
55+
5356
v2.2.4 (September 11, 2018)
5457
-----------------------------------------------------
5558

include/pybind11/cast.h

+42-2
Original file line numberDiff line numberDiff line change
@@ -1496,9 +1496,46 @@ struct copyable_holder_caster : public type_caster_base<type> {
14961496
holder_type holder;
14971497
};
14981498

1499-
/// Specialize for the common std::shared_ptr, so users don't need to
1499+
/// Specialize type_caster for std::shared_ptr<T>.
1500+
/// This is the same as copyable_holder_caster, except that when casting to C++
1501+
/// we keep the Python object alive through the shared_ptr as e.g. virtual
1502+
/// functions and derived state might be defined there.
15001503
template <typename T>
1501-
class type_caster<std::shared_ptr<T>> : public copyable_holder_caster<T, std::shared_ptr<T>> { };
1504+
class type_caster<std::shared_ptr<T>>
1505+
{
1506+
PYBIND11_TYPE_CASTER (std::shared_ptr<T>, _(PYBIND11_STRING_NAME));
1507+
1508+
// Re-use copyable_holder_caster
1509+
using BaseCaster = copyable_holder_caster<T, std::shared_ptr<T>>;
1510+
1511+
bool load (pybind11::handle src, bool b)
1512+
{
1513+
BaseCaster bc;
1514+
bool success = bc.load (src, b);
1515+
if (!success)
1516+
{
1517+
return false;
1518+
}
1519+
1520+
// * Get src as a py::object
1521+
// * Construct a shared_ptr to the py::object
1522+
auto py_obj = reinterpret_borrow<object> (src);
1523+
auto py_obj_ptr = std::make_shared<object> (py_obj);
1524+
1525+
// * Use BaseCaster to get it as the shared_ptr<T>
1526+
// * Use this to make an aliased shared_ptr<T> that keeps the py::object alive
1527+
auto base_ptr = static_cast<std::shared_ptr<T>> (bc);
1528+
value = std::shared_ptr<T> (py_obj_ptr, base_ptr.get ());
1529+
return true;
1530+
}
1531+
1532+
static handle cast (std::shared_ptr<T> sp,
1533+
return_value_policy rvp,
1534+
handle h)
1535+
{
1536+
return BaseCaster::cast (sp, rvp, h);
1537+
}
1538+
};
15021539

15031540
template <typename type, typename holder_type>
15041541
struct move_only_holder_caster {
@@ -1540,6 +1577,9 @@ template <typename base, typename holder> struct is_holder_type :
15401577
template <typename base, typename deleter> struct is_holder_type<base, std::unique_ptr<base, deleter>> :
15411578
std::true_type {};
15421579

1580+
template <typename T>
1581+
struct is_holder_type<T, std::shared_ptr<T>> : std::true_type {};
1582+
15431583
template <typename T> struct handle_type_name { static constexpr auto name = _<T>(); };
15441584
template <> struct handle_type_name<bytes> { static constexpr auto name = _(PYBIND11_BYTES_NAME); };
15451585
template <> struct handle_type_name<args> { static constexpr auto name = _("*args"); };

tests/test_virtual_functions.py

+2
Original file line numberDiff line numberDiff line change
@@ -388,5 +388,7 @@ def f(self, value):
388388
print("Right one", 42)
389389

390390
holder = m.SharedPtrHolder(Derived())
391+
# At this point, our 'Derived' instance has gone out of scope in Python but
392+
# is being kept alive by a shared_ptr inside 'holder'.
391393
holder.run(42);
392394
assert somelist == [42]

0 commit comments

Comments
 (0)