Skip to content

Possible memory issues when using shared_ptr as holder type #1937

Description

@lyskov

Dear Pybind11 developers, i would like to report a strange case which on a surface looks like a situation when C++ fail to retain ownership of classes defined in Python and derived from C++ base classes.

Below is a minimal example to reproduce this. Note that Python script have --bug command line argument which allow to trigger a bug. And if option is not supplied then code work as expected without any errors.

In short: issues seems to arise when derived class is created in C++ by a 'clone' method. It looks like after creation 'Python' portion of Snake class will get immediately deleted unless it explicitly stored in one of Python variables.

When --bug is supplied execution terminated with error message:

Snake.clone!
Traceback (most recent call last):
File "inheritance-clone.py", line 34, in <module>
example.clone_and_call_go(a)
RuntimeError: Tried to call pure virtual function "Animal::go"
#include<pybind11/pybind11.h>classAnimal {
public:virtual~Animal() { }
virtual std::string go(int n_times) = 0;
virtual std::shared_ptr<Animal> clone() = 0;
};
std::string clone_and_call_go(std::shared_ptr<Animal> animal) {
auto new_animal = animal->clone();
return new_animal->go(3);
}
classPyAnimal : publicAnimal {
public:/* Inherit the constructors */using Animal::Animal;
/* Trampoline (need one for each virtual function) */
std::string go(int n_times) override {
PYBIND11_OVERLOAD_PURE(
std::string, /* Return type */
Animal, /* Parent class */
go, /* Name of function in C++ (must match Python name) */
n_times /* Argument(s) */
);
}
std::shared_ptr<Animal> clone() override {
PYBIND11_OVERLOAD_PURE(
std::shared_ptr<Animal>,
Animal,
clone
);
}
};
namespacepy= pybind11;
PYBIND11_MODULE(example, m) {
py::class_<Animal, std::shared_ptr<Animal>, PyAnimal>(m, "Animal")
.def(py::init<>())
.def("go", &Animal::go)
.def("clone", &Animal::clone)
;
m.def("clone_and_call_go", &clone_and_call_go);
}
importargparseimportexampleclassSnake(example.Animal):
def__init__(self, bug):
#super().__init__()example.Animal.__init__(self)
self.copies_= []
self.bug_=bugdefgo(self, n_times):
s='Snake.go({})'.format(n_times)
print(s)
returnsdefclone(self):
print('Snake.clone!')
copy=Snake(bug=self.bug_)
ifnotself.bug_:
self.copies_.append(copy)
returncopyif__name__=="__main__":
parser=argparse.ArgumentParser()
parser.add_argument("--bug", action="store_true", help="Disables the workaround, showcases the bug")
args=parser.parse_args()
a=Snake(args.bug)
example.clone_and_call_go(a)

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions