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
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions