Swig从Base *向下转换为Derived *

时间:2014-12-10 02:55:55

标签: python c++ swig downcast

我有以下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中,我可以在Entity实例上成功调用component("DerivedComponent").update()。但是,我无法致电component("DerivedComponent").speak(),因为component("DerivedComponent")返回的类型报告为<class 'module.Component'>

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

如果没有在c ++中定义一大堆类型转换函数并将它们暴露给Python,那么使用Swig或Python进行向下转换是否有更好的解决方案?我有什么选择?

2 个答案:

答案 0 :(得分:5)

你可以通过一点点工作完成你想要的Python。它可以正常工作,因为在Python中,向下转换是没有意义的,因为函数的返回类型(或一般类型)不是强类型的,所以我们可以修改你的Entity::component函数以始终返回最派生类型no不管它是什么。

要使用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()

答案 1 :(得分:1)

我希望做类似的事情,并提出了基于this question的类似但不同的解决方案。

如果您提前知道可能的类型并且不介意额外的开销,那么您可以使用&#39; out&#39; typemap循环遍历和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);