std::enable_shared_from_this loses std::shared_ptr from aliasing constructor
Nobody has claimed this yet.
- Dominant language
- C++
- Stars
- 18k
- Forks
- 2.3k
- Avg merge
- 5d 17h
- Merged PRs (30d)
- 10
Description
Issue description
A returned std::shared_ptr created via the "aliasing constructor" gets lost when the class inherits from std::enable_shared_from_this. I've tracked this down and it appears that class_::init_holder (variant 1) will use shared_from_this() to initialize a holder in this case. The returned pointer created with the aliasing constructor is just immediately destroyed in favor of the one from shared_from_this().
Removing that code block gives the expected behavior, but I've temporarily worked around it by implementing my own version of enable_shared_from_this. That tricks pybind into not using that variant of init_holder. However, if there's a way to make this work with std::enable_shared_from_this, that would be preferred.
Reproducible example code
C++
#include <iostream>
#include <memory>
#include <string>
#include <pybind11/pybind11.h>
namespace py = pybind11;
struct SharedFromThis : public std::enable_shared_from_this<SharedFromThis>
{
SharedFromThis() { std::cout << "SharedFromThis" << std::endl; }
~SharedFromThis() { std::cout << "~SharedFromThis" << std::endl; }
};
struct NotSharedFromThis {
NotSharedFromThis() { std::cout << "NotSharedFromThis" << std::endl; }
~NotSharedFromThis() { std::cout << "~NotSharedFromThis" << std::endl; }
};
template<typename T>
struct Tracker {
std::shared_ptr<T> tracked;
Tracker(const std::shared_ptr<T> _tracked)
: tracked(_tracked) {
std::cout << "Tracker" << std::endl;
}
~Tracker() { std::cout << "~Tracker" << std::endl; }
};
template<typename T>
std::shared_ptr<T> track() {
auto instance = std::make_shared<T>();
auto tracker = std::make_shared<Tracker<T>>(instance);
return std::shared_ptr<T>(tracker, instance.get());
}
PYBIND11_MODULE(shared_from_this_example, m) {
py::class_<SharedFromThis, std::shared_ptr<SharedFromThis>>(m, "SharedFromThis");
py::class_<NotSharedFromThis, std::shared_ptr<NotSharedFromThis>>(m, "NotSharedFromThis");
m.def("track_shared", &track<SharedFromThis>);
m.def("track_not_shared", &track<NotSharedFromThis>);
}
Python
import shared_from_this_example
def test(track):
print('-' * 10)
instance = track()
print('--release--')
instance = None
print('-' * 10)
test(shared_from_this_example.track_shared)
test(shared_from_this_example.track_not_shared)
Output
----------
SharedFromThis
Tracker
~Tracker
--release--
~SharedFromThis
----------
----------
NotSharedFromThis
Tracker
--release--
~Tracker
~NotSharedFromThis
----------
Notice that when inheriting from std::enable_shared_from_this, the Tracker object is destroyed before the held object is released.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with class_::init_holder (variant 1) in include/pybind11/pybind11.h and run the C++/Python reproducer from the issue. Trace how std::enable_shared_from_this handles the aliasing-constructor pointer; done means the Tracker remains alive until the held object is released, as in the non-enable_shared_from_this case.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- cpp
- Domain
- tooling
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 45/100