Skip to content

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

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
3 tasks done
fangchaooo opened this issue Mar 14, 2025 · 4 comments
Open
3 tasks done
Labels
triage New bug, unverified

Comments

@fangchaooo
Copy link

fangchaooo commented Mar 14, 2025

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

@fangchaooo fangchaooo added the triage New bug, unverified label Mar 14, 2025
@fangchaooo
Copy link
Author

Compile: c++ -O3 -Wall -shared -std=c++11 -fPIC $(python3 -m pybind11 --includes) example.cpp -o example$(python3-config --extension-suffix)

Python 3.9.21 (main, Mar 12 2025, 20:46:55)
[GCC 9.4.0] on linux
Type "help", "copyright", "credits" or "license" for more information.

@fangchaooo
Copy link
Author

class PyA : public A, public py::trampoline_self_life_support{
public:
    using A::A;
    void go() override {
        PYBIND11_OVERLOAD_PURE(
            void,  // 返回类型
            A,     // 父类
            go     // 方法名
        );
    }
};


I change my class using trampoline_self_life_support, but it still "RuntimeError: Tried to call pure virtual function "A::go""

@fangchaooo
Copy link
Author

fangchaooo commented Mar 14, 2025

@rwgk Very thanks for your work! But for this issue type, can you support it! I find many this type issue question.

@fangchaooo
Copy link
Author

I solve it!

#include <pybind11/pybind11.h>
#include <pybind11/functional.h>  // 支持 std::function 类型
#include <pybind11/trampoline_self_life_support.h>
#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::unique_ptr<A>(const std::string&)>;

class PyA : public A, public py::trampoline_self_life_support{
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::unique_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) {
    py::class_<A, PyA,py::smart_holder>(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);  // 关键点在这里

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
triage New bug, unverified
Projects
None yet
Development

No branches or pull requests

1 participant