【问题标题】:Swig downcasting from Base* to Derived*从 Base* 向下转换到 Derived*
【发布时间】:2014-12-10 02:55:55
【问题描述】:

我使用 SWIG 向 Python 公开了以下 c++ 类(简化):

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") 返回的类型报告为&lt;class 'module.Component'&gt;

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

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

【问题讨论】:

  • 你不能在 C++ 中调用该方法而不进行向下转换,那么你为什么认为你可以在 Python 中侥幸逃脱呢?您需要在 C++ 中定义类型转换函数并公开它们。
  • @MarkTolonen 这并不是说我认为我可以在不进行类型转换的情况下逃脱。正如 Flexo 的回答所示,我只是希望有一种方法可以使流程自动化。

标签: python c++ swig downcast


【解决方案1】:

只需一点点工作,您就可以在 Python 中做您想做的事。它可以按您希望的方式工作,因为在 Python 中向下转换是没有意义的,因为函数的返回类型(或一般类型)不是强类型的,因此我们可以修改您的 Entity::component 函数以始终返回最派生的类型,无论如何这是。

要使其与您的 C++/Python 绑定一起工作,您需要为 Entity::component 编写一个“输出”类型映射。我写了一个例子来说明它是如何工作的。在这种情况下,我们必须稍微犹豫一下,因为知道将其向下转换为什么的唯一方法来自函数的参数。 (例如,如果您的基类有一个将其作为字符串/枚举返回的方法,您可以进一步简化它而不依赖于输入参数)。

%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()

【讨论】:

  • 完美运行。谢谢你的详细回答。这正是我希望存在的那种解决方案。
  • 也许我运气不好,但你知道是否有办法向下转换为继承自 Component 的 Python 类?我正在考虑通过检查 SWIG_NewPointerObj() 是否返回 NULL 来扩展您的方法,这意味着该类尚未暴露给 SWIG,如果是,则使用 PyObject_SetAttrString($result, "__class__", pyobject_python_class) 设置 __class__ 属性。但似乎这被__setattr__ 截获,SWIG 在生成的 python 文件中重载。
  • @Homar 我不太了解这个问题 - 为什么需要这样做? (它也不能与 SWIG -builtin 一起正常工作)。您能否通过一个完整的最小示例提出另一个问题,展示 C++ 需要是什么以及您想要的理想 Python 语法?
  • 我发布了另一个问题,希望能阐明我想要实现的目标:stackoverflow.com/questions/27454289/…
  • 这很有帮助。谢谢。
【解决方案2】:

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

如果您提前知道可能的类型并且不介意额外的开销,您可以让 '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);

【讨论】:

    猜你喜欢
    • 2010-11-24
    • 2019-05-24
    • 1970-01-01
    • 1970-01-01
    • 2016-07-23
    • 2018-12-15
    • 1970-01-01
    • 2010-10-12
    • 1970-01-01
    相关资源
    最近更新 更多