PyBind11传递Python子类至C++ RuleSet时动态类型丢失问题
问题:Python子类化C++抽象类后存入RuleSet丢失动态类型,触发纯虚函数调用错误
我有一个C++类RuleSet,其中包含指向胜利条件的std::shared_ptr<VictoryRule>。VictoryRule是抽象类型,定义如下:
class VictoryRule { public: //! @brief Check if game is won by a team. //! @return the teamno of the team that won or team_t::no_team if nobody won. virtual std::vector<team_t> winners(const Game &game) = 0; //! @brief Default virtual destructor. virtual ~VictoryRule() = default; };
我通过pybind11将该类暴露给Python,使用trampoline类PyVictoryRule支持Python子类化:
class PyVictoryRule : public VictoryRule { public: std::vector<team_t> winners(const Game &game) override { PYBIND11_OVERRIDE_PURE(std::vector<team_t>, VictoryRule, winners, game); } };
注册代码:
pybind11::class_<VictoryRule, PyVictoryRule, std::shared_ptr<VictoryRule>>(m, "VictoryRule") .def(pybind11::init()) .def("winners", &VictoryRule::winners, arg("game"));
在Python中实现自定义子类:
class CustomVictoryRule(VictoryRule): def __init__(self): VictoryRule.__init__(self) def winners(self, game): return [1]
直接实例化调用winners方法正常:
cvr = CustomVictoryRule() cvr.winners() # 返回 [1]
但存入C++的RuleSet后出现问题,RuleSet定义:
class RuleSet { public: std::shared_ptr<VictoryRule> get_victory_rule(); void register_victory_rule(std::shared_ptr<VictoryRule> victory_rule); private: std::unordered_map<CasePawn::Type, std::shared_ptr<Rule>> per_pawn_type_rule_; std::shared_ptr<VictoryRule> victory_rule_; };
RuleSet的pybind11注册代码:
pybind11::class_<RuleSet, std::shared_ptr<RuleSet>>(m, "RuleSet") .def(pybind11::init()) .def("get_victory_rule", &RuleSet::get_victory_rule) .def("register_victory_rule", &RuleSet::register_victory_rule, arg("victory_rule"));
传入CustomVictoryRule到register_victory_rule后,调用get_victory_rule().winners()触发错误:
ruleset = RuleSet() ruleset.register_victory_rule(CustomVictoryRule()) ruleset.get_victory_rule().winners() # 报错:RuntimeError: Tried to call pure virtual function "VictoryRule::winners"
请问如何处理才能让ruleset.get_victory_rule()返回正确的CustomVictoryRule类型,正常调用其winners方法?
解决方案
1. 保留Python实例的引用,避免GC回收
这是最常见的问题根源:直接传递临时对象CustomVictoryRule()给C方法时,Python侧没有持有该实例的引用,垃圾回收器会回收这个对象,导致C的shared_ptr指向的只剩下VictoryRule基类的空壳,调用虚函数时触发纯虚函数错误。
修改Python代码,先将实例存入变量再传入:
ruleset = RuleSet() cvr = CustomVictoryRule() # 持有引用,防止GC回收 ruleset.register_victory_rule(cvr) ruleset.get_victory_rule().winners(game) # 正常调用
2. 确认pybind11类型绑定的完整性
- 确保
team_t枚举类型已经正确绑定到Python(比如注册为int或者对应的枚举类),如果类型转换失败,也可能导致trampoline函数无法正确调用Python实现。 - 注册
VictoryRule时,可尝试添加pybind11::dynamic_attr()来支持Python子类的动态属性,帮助保留类型信息:pybind11::class_<VictoryRule, PyVictoryRule, std::shared_ptr<VictoryRule>>(m, "VictoryRule") .def(pybind11::init()) .def("winners", &VictoryRule::winners, arg("game")) .def(pybind11::dynamic_attr()); // 添加此行
3. 确保C++侧的shared_ptr正确传递类型信息
当前RuleSet直接存储std::shared_ptr<VictoryRule>,不会发生对象切片问题,只需确保register_victory_rule仅做指针存储操作,不对对象进行不必要的拷贝即可。
内容的提问来源于stack exchange,提问作者Lærne
相关产品推荐
相关产品推荐

