从基础*向下摇摆到派生*

Swig downcasting from Base* to Derived*

本文关键字:摇摆 派生      更新时间:2023-10-16

我有以下 c++ 类(简化(,我使用 SWIG 向 Python 公开

struct Component
{
    virtual void update();
}
struct DerivedComponent : public Component
{
    void update() { cout << "DerivedComponent::update()" << endl; }
    void speak() { cout << "DerivedComponent::speak()" << endl; }
}
class Entity
{
public:
    Component* component(const std::string& class_name)
    {
        return m_components[class_name];
    }
private:
    std::unordered_map<std::string, Component*> m_components;
}

现在,在 Python 中,我可以在实体实例上成功调用component("DerivedComponent").update()。但是,我无法调用component("DerivedComponent").speak(),因为component("DerivedComponent")返回的类型被报告为<class 'module.Component'>

我显然需要向下转换component()函数的结果才能调用DerivedComponent中定义的方法。我曾希望Swig能够像我相信Boost.Python那样执行自动下投。

除了在 c++ 中定义一大堆类型转换函数并将它们公开给 Python 之外,有没有更好的解决方案来使用 Swig 或 Python 进行向下转换?我有哪些选择?

你可以在Python中做你想做的事,只需要一点点工作。它的工作方式如您所愿,因为在 Python 中,向下转换毫无意义,因为函数的返回类型(或一般类型(不是强类型的,因此我们可以修改您的Entity::component函数以始终返回派生最多的类型,无论它是什么。

要使其适用于您的 C++/Python 绑定,您需要为 Entity::component 编写一个"out"类型映射。我已经写了一个它如何工作的例子。在这种情况下,我们必须稍微改变一下,因为知道将其向下抛弃的唯一方法来自函数的参数。(例如,如果您的基类有一个将其作为字符串/枚举返回的方法,则可以进一步简化它,而不依赖于输入参数(。

%module test
%{
#include "test.hh"
%}
%include <std_string.i>
%typemap(out) Component * Entity::component {
    const std::string lookup_typename = *arg2 + " *";
    swig_type_info * const outtype = SWIG_TypeQuery(lookup_typename.c_str());
    $result = SWIG_NewPointerObj(SWIG_as_voidptr($1), outtype, $owner);
}
%include "test.hh"

这使用 SWIG_TypeQuery 函数要求 Python 运行时基于 arg2(例如字符串(查找类型。

我必须对您的示例标头(在我的示例中名为test.hh(进行一些更改以解决一些问题,然后才能将其制作成一个完全有效的演示,它最终看起来像:

#include <iostream>
#include <map>
#include <string>
struct Component
{
    virtual void update() = 0;
    virtual ~Component() {}
};
struct DerivedComponent : public Component
{
    void update() { std::cout << "DerivedComponent::update()" << std::endl; }
    void speak() { std::cout << "DerivedComponent::speak()" << std::endl; }
};
class Entity
{
public:
    Entity() {
       m_components["DerivedComponent"] = new DerivedComponent;
    }
    Component* component(const std::string& class_name)
    {
        return m_components[class_name];
    }
private:
    std::map<std::string, Component*> m_components;
};

然后我用:

swig -py3 -c++ -python -Wall test.i
g++ -Wall -Wextra test_wrap.cxx -I/usr/include/python3.4/ -lpython3.4m -shared -o _test.so

有了这个,我就可以运行以下Python:

from test import *
e=Entity()
print(e)
c=e.component("DerivedComponent")
print(c)
print(type(c))
c.update()
c.speak()

这如您所希望的那样工作:

<test.Entity; proxy of <Swig Object of type 'Entity *' at 0xb7230458> >
Name is: DerivedComponent *, type is: 0xb77661d8
<test.DerivedComponent; proxy of <Swig Object of type 'DerivedComponent *' at 0xb72575d8> >
<class 'test.DerivedComponent'>
DerivedComponent::update()
DerivedComponent::speak()

我想做类似的事情,并根据这个问题提出了一个类似但不同的解决方案。

如果您提前知道可能的类型并且不介意额外的开销,则可以让"out"类型图循环并dynamic_cast到每个类型,以自动返回对象及其真实类型。SWIG 已经为具有 %factory 功能的指针实现了这一点:

%factory(Component* /* or add method name. this is just the typemap filter */,
     DerivedComponent1,
     DerivedComponent2);

查看 factory.swg 和 boost_shared_ptr.i,我也得到了这个shared_ptr和dynamic_pointer_cast的工作:

%define %_shared_factory_dispatch(Type)
if (!dcast) {
  SWIG_SHARED_PTR_QNAMESPACE::shared_ptr<Type> dobj
          = SWIG_SHARED_PTR_QNAMESPACE::dynamic_pointer_cast<Type>($1);
  if (dobj) {
    dcast = 1;
    SWIG_SHARED_PTR_QNAMESPACE::shared_ptr<Type> *smartresult
            = dobj ? new SWIG_SHARED_PTR_QNAMESPACE::shared_ptr<Type>(dobj) : 0;
    %set_output(SWIG_NewPointerObj(%as_voidptr(smartresult),
                                   $descriptor(SWIG_SHARED_PTR_QNAMESPACE::shared_ptr<Type> *),
                                   SWIG_POINTER_OWN));
  }
}%enddef
%define %shared_factory(BaseType,Types...)
%typemap(out) SWIG_SHARED_PTR_QNAMESPACE::shared_ptr<BaseType> {
  int dcast = 0;
  %formacro(%_shared_factory_dispatch, Types)
  if (!dcast) {
      SWIG_SHARED_PTR_QNAMESPACE::shared_ptr<BaseType> *smartresult
              = $1 ? new SWIG_SHARED_PTR_QNAMESPACE::shared_ptr<BaseType>($1) : 0;
      %set_output(SWIG_NewPointerObj(%as_voidptr(smartresult),
                                     $descriptor(SWIG_SHARED_PTR_QNAMESPACE::shared_ptr<BaseType> *),
                                     SWIG_POINTER_OWN));
  }
}%enddef
// Apply dynamic_pointer cast to all returned shared_ptrs of this type
%factory(Component /* must be a type for shared_ptr */,
     DerivedComponent1,
     DerivedComponent2);