Skip to content

Possible memory issues when using shared_ptr as holder type #1937

@lyskov

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>


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

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