pybind / pybind/pybind11

std::enable_shared_from_this loses std::shared_ptr from aliasing constructor

Open
#1,688 0 comments 2 reactions 0 assignees View on GitHub

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

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.