从 Base* 到 Derived* 的 Swig 向下转换 [英] Swig downcasting from Base* to Derived*

查看:39
本文介绍了从 Base* 到 Derived* 的 Swig 向下转换的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有以下 C++ 类(简化),我使用 SWIG 将它们暴露给 Python:

I have the following c++ classes (simplified) which I am exposing to Python using SWIG:

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'>.

Now, in Python I can successfully call component("DerivedComponent").update() on an Entity instance. However, I cannot call component("DerivedComponent").speak() since the type returned by component("DerivedComponent") is reported as <class 'module.Component'>.

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

I obviously need to downcast the result of the component() function in order to call methods defined in DerivedComponent. I had hoped that Swig would perform automatic downcasting like I believe that Boost.Python does.

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

Short of defining a whole bunch of typecasting functions in c++ and exposing them to Python, is there any better solution for downcasting using either Swig or Python? What are my options?

推荐答案

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

You can do exactly what you want in Python, with a little work. It works as you hope because in Python downcasting is kind of meaningless as the return types of functions (or types in general) aren't strongly typed, so we can modify your Entity::component function to always return the most derived type no matter what it is.

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

To make that work with your C++/Python binding you need to write an 'out' typemap for Entity::component. I've written an example of how it might work. In this case we have to bodge it slightly because the only way to know what to downcast it to comes from the argument to the function. (If for example your base class had a method that returned this as a string/enum you could simplify this further and not depend on the input arguments).

%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(在您的示例中是字符串)查找类型.

This uses the SWIG_TypeQuery function to ask the Python runtime to lookup the type based on arg2 (which for your example is the string).

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

I had to make some changes to your example header (named test.hh in my example) to fix a few issues before I could make this into a fully working demo, it ended up looking like:

#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:

With this in place I could then run the following 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()

这篇关于从 Base* 到 Derived* 的 Swig 向下转换的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆