为什么SWIG似乎破坏了成员类的内容?

时间:2013-11-17 09:57:35

标签: python swig

当一个类包含指向另一个类的指针时,SWIG会不一致地报告另一个类的内容。这是最小的可重复示例(SSCCE):

的config.h:

class Config
{
    int debug;      
public:
    void showDebug(void);
};

class ConfigContainer
{
    Config *config; 
public:
    ConfigContainer(Config *);
    void showDebug(void);
};

Config.cpp:

#include <iostream>
#include "Config.h"

using namespace std;

void Config::showDebug(void) {
    cout << "debug address: " << &debug << " contents: " << debug << endl;
}

ConfigContainer::ConfigContainer(Config *cfg)
{
        config= cfg;
}

void ConfigContainer::showDebug(void)
{
    config->showDebug();
}

现在当我用SWIG将其翻译成Python时,我得到了这个:

>>>>c = ConfigContainer(Config())
>>>>c.showDebug()
debug address: 0xabf380 contents: 11586464
>>> c.showDebug()
debug address: 0xabf380 contents: 11067216

当我单独使用C ++运行此序列时,报告的内容是相同的。但是对于SWIG,即使地址相同,SWIG也会破坏该地址内的值。

响应者说,这是因为ConfigContainer的Config *config成员,一旦调用showDebug(),就会在Python中减少引用计数。

如何告诉SWIG,告诉Python单独留下config成员?

2 个答案:

答案 0 :(得分:3)

当Python创建您的Config对象时,它不会持有对它的永久引用。你的ConfigContainer类只有一个简单的指针,所以即使在C ++中,如果你不保持对象存活,ConfigContainer也不会知道它。

以下行让Python创建了一个临时的Config对象,当该行完成时会被销毁:

c = ConfigContainer(Config())

如果你将脚印添加到构造函数和析构函数中,你可以看到这一点,就像我在下面所做的那样:

>>> import x
>>> c = x.ConfigContainer(x.Config())
__cdecl Config::Config(void)
__cdecl ConfigContainer::ConfigContainer(class Config *)
__cdecl Config::~Config(void)

所以现在ConfigContainer持有一个被破坏的指针。简单的解决方案是保持对Config的引用,直到完成为止。

>>> import x
>>> c = x.Config()
__cdecl Config::Config(void)
>>> cc = x.ConfigContainer(c)
__cdecl ConfigContainer::ConfigContainer(class Config *)

复杂的解决方案是实现引用计数(参见6.25 C++ reference counted objects - ref/unref feature)。

另一种解决方案是通过更改容器来使用SWIG对std::shared_ptr的支持:

class ConfigContainer
{
    std::shared_ptr<Config> config; 
public:
    ConfigContainer(std::shared_ptr<Config>&);
    ~ConfigContainer();
    void showDebug(void);
};

接口文件中需要以下内容:

%include <std_shared_ptr.i>
%shared_ptr(Config)          // This instantiates the template for SWIG

现在Config将被引用计数:

>>> import x
>>> cc = x.ConfigContainer(x.Config())
__cdecl Config::Config(void)
__cdecl ConfigContainer::ConfigContainer(class std::shared_ptr<class Config> &)

ConfigContainer现在提到Config。销毁容器会破坏对Config的最后一次引用:

>>> del cc
__cdecl ConfigContainer::~ConfigContainer(void)
__cdecl Config::~Config(void)

但是如果Python有自己的引用,那么只有当Python完成后才会破坏容器:

>>> c = x.Config()
__cdecl Config::Config(void)
>>> cc = x.ConfigContainer(c)
__cdecl ConfigContainer::ConfigContainer(class std::shared_ptr<class Config> &)
>>> del cc
__cdecl ConfigContainer::~ConfigContainer(void)
>>> del c
__cdecl Config::~Config(void)

答案 1 :(得分:2)

这看起来像是终身问题。

如果一个指针传递给一个函数的对象将被保存在某个地方,你需要通过递增引用计数器来告知Python运行时。

如果你不这样做,那么在第一次调用终止后对象可能会被销毁,所以你的config指针实际指向Python认为可以重用的内存。

您所观察到的是内存实际上已经被重用于其他对象(因此您看到内容发生了变化)。

在cpython对象中,使用引用计数器管理生命周期(使用单独的收集算法来解决引用循环的问题)。每个对象都有一个计数器,指示有多少其他对象指向它,并且每次使用时都必须保持此计数器正确。

如果传递给一个函数一个对象,并且函数代码将指针存储到某个对象,那么它也应该增加指向对象的引用计数器。如果它无法通知新存储引用的对象,则问题是该对象可能被销毁,留下悬挂指针指向未分配(或以后重用)的内存。

相反的问题(过多地增加引用)是内存泄漏,因为如果引用计数器大于零,即使没有人使用它,Python内存管理器也不会回收对象内存。

请参阅http://docs.python.org/2/c-api/refcounting.html及相关文档。