-
Notifications
You must be signed in to change notification settings - Fork 2.3k
Closed
Description
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>
class Animal {
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);
}
class PyAnimal : public Animal {
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
);
}
};
namespace py = 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);
}import argparse
import example
class Snake(example.Animal):
def __init__(self, bug):
#super().__init__()
example.Animal.__init__(self)
self.copies_ = []
self.bug_ = bug
def go(self, n_times):
s = 'Snake.go({})'.format(n_times)
print(s)
return s
def clone(self):
print('Snake.clone!')
copy = Snake(bug=self.bug_)
if not self.bug_:
self.copies_.append(copy)
return copy
if __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)Metadata
Metadata
Assignees
Labels
No labels