You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.23 13:48:23