Skip to content

[BUG]: Pure Virtual Function Call When Creating Python-derived Instance via C++ Callback #5560

@fangchaooo

Description

@fangchaooo

Required prerequisites

What version (or hash if on master) of pybind11 are you using?

d28904f

Problem description

When creating a Python-derived class from a C++ base class (with a pure virtual function) via a callback, the Python override is not invoked. Instead, the program attempts to call the pure virtual function (A::go), resulting in a runtime error and segmentation fault.

Expected Behavior:
The overridden Python method (e.g., PyDerivedA.go) should be called, producing the expected output (e.g., "PyDerivedA go called!").

Actual Behavior:
The callback returns an instance of the Python-derived class, but due to improper conversion, the object loses its Python override bindings. When MyRun::Run is executed, it calls the pure virtual function A::go, triggering a runtime error and segmentation fault.

build git:(master) python test.py
A
Traceback (most recent call last):
  File "/home/mi/fc4tdisk/code/pybind11/build/test.py", line 19, in <module>
    runner.run("derived")  # 输出 "PyDerivedA go called!"
RuntimeError: Tried to call pure virtual function "A::go"

Reproducible example code

#include <pybind11/pybind11.h>
#include <pybind11/functional.h>  // 支持 std::function 类型
#include <memory>
#include <unordered_map>
#include <iostream>
#include <string>
#include <functional>
namespace py = pybind11;



class A {
public:
    A() { std::cout << "A" << std::endl; }
    virtual void go() = 0;
    virtual ~A()=default;
};
using CreationEvaluatorCallback = std::function<std::shared_ptr<A>(const std::string&)>;
class PyA : public A {
public:
    using A::A;
    void go() override {
        PYBIND11_OVERLOAD_PURE(
            void,  // 返回类型
            A,     // 父类
            go     // 方法名
        );
    }
};


class MyFactory {
public:
    static MyFactory& Instance() {
        static MyFactory instance; // C++11 保证局部静态变量线程安全
        return instance;
    }
    void Registry(const std::string &name, CreationEvaluatorCallback callback) {
        callback_registry_[name] = callback;
    }

    std::shared_ptr<A> Create(const std::string &evaluator_name) {
        auto iter = callback_registry_.find(evaluator_name);
        if (iter != callback_registry_.end()) {
            return iter->second(evaluator_name);
        }
        // 如果未找到对应的回调,可根据需要返回 nullptr 或抛出异常
        return nullptr;
    }

private:
    MyFactory() = default;
    ~MyFactory() = default;

    // 禁用拷贝构造和赋值运算符
    MyFactory(const MyFactory&) = delete;
    MyFactory& operator=(const MyFactory&) = delete;

    std::unordered_map<std::string, CreationEvaluatorCallback> callback_registry_;
};

class MyRun {
public:
    MyRun(){}
    ~MyRun(){}
    void Run(const std::string &evaluator_name){
        auto eval = MyFactory::Instance().Create(evaluator_name);
        eval->go();
    }
};


PYBIND11_MODULE(example, m) {
    m.doc() = "pybind11 包装 MyFactory 和 MyRun 示例";

    py::class_<A, PyA, std::shared_ptr<A>>(m, "A")
        .def(py::init<>())
        .def("go", &A::go);

    py::class_<MyFactory, std::unique_ptr<MyFactory, py::nodelete>>(m, "MyFactory")
        .def_static("instance", &MyFactory::Instance, py::return_value_policy::reference)
        .def("registry", &MyFactory::Registry)
        .def("create", &MyFactory::Create);

    // 包装 MyRun
    py::class_<MyRun>(m, "MyRun")
        .def(py::init<>())
        .def("run", &MyRun::Run);
}



import example

# 例如在 Python 中定义一个 A 的子类
class PyDerivedA(example.A):
    def __init__(self):
        super().__init__()
        print("111111111111111111")
        
    def go(self):
        print("PyDerivedA go called!")

# 注册回调,将字符串 "derived" 与创建 PyDerivedA 实例的回调绑定
def create_derived(name):
    return PyDerivedA()

factory = example.MyFactory.instance()
factory.registry("derived", create_derived)

# 调用 MyRun 执行 go 方法
runner = example.MyRun()
runner.run("derived")  # 输出 "PyDerivedA go called!"

Is this a regression? Put the last known working version here if it is.

not a regression

Metadata

Metadata

Assignees

No one assigned

    Labels

    triageNew bug, unverified

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions