Skip to content

[BUG]: py::smart_holder does not preserve shared_ptr ownership across casts (weak_ptr expires unexpectedly) #6021

@sergeyy32

Description

@sergeyy32

Required prerequisites

What version (or hash if on master) of pybind11 are you using?

3.0.2

Problem description

Hello,

When using py::smart_holder as the holder type, casting a Python-owned object to std::shared_ptr does not seem to preserve shared ownership across casts.

As a result, a std::weak_ptr stored from a previous call cannot be locked later, even though the Python object is still alive.

With the classic std::shared_ptr holder, the behavior works as expected.

Below is a minimal reproducible example.

#include <pybind11/pybind11.h>
#include <iostream>
#include <memory>

namespace py = pybind11;

class Base : public std::enable_shared_from_this<Base> {
public:
    Base() = default;
    virtual ~Base() = default;
};

template<class PyExtBase = Base>
class PyBase
    : public PyExtBase
    , public py::trampoline_self_life_support {
public:
    using PyExtBase::PyExtBase;
};

PYBIND11_MODULE(example, m) {
    py::classh<Base, PyBase<>>(m, "Base")
        .def(py::init<>());

    m.def("get_base", [](py::type type) {
        auto b = py::cast<std::shared_ptr<Base>>(type());
        return b;
    });

    m.def("hold_weak", [](std::shared_ptr<Base> b) {
        static std::weak_ptr<Base> b_weak;

        if (auto locked = b_weak.lock()) {
            std::cout << "Current weak pointer use count: "
                      << locked.use_count() << std::endl;
        } else {
            std::cout << "No weak pointer provided." << std::endl;
        }

        b_weak = b;
    });
}

Python usage

import example

class DerivedClass(example.Base):
    def __init__(self):
        super().__init__()

b = example.get_base(DerivedClass)
example.hold_weak(b)
example.hold_weak(b)

Observed output (with py::smart_holder):

No weak pointer provided.
No weak pointer provided.

Observed output (with classic holder)

py::class_<Base, std::shared_ptr<Base>, PyBase<>>(m, "Base")

The output becomes:

No weak pointer provided.
Current weak pointer use count: 4

Is this the intended behavior of py::smart_holder, or could this be a bug?

If this is expected behavior, what would be the recommended way to preserve std::weak_ptr semantics when ownership is managed via smart_holder?

Reproducible example code


Is this a regression? Put the last known working version here if it is.

Not a regression

Metadata

Metadata

Assignees

No one assigned

    Labels

    triageNew bug, unverified

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions