当一个类包含指向另一个类的指针时,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
成员?
答案 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内存管理器也不会回收对象内存。