SWIG Python包装器中临时对象的生命周期(?)

Man*_*agu 8 c++ python swig lifetime temporary-objects

2月12日编辑

我最近刚刚使用一些SWIG生成的Python包装器为一些C++类提出了一个奇怪的崩溃.似乎SWIG和Python的结合有点急于清理临时值.事实上,他们非常渴望在他们还在使用的时候进行清理.一个显着浓缩的版本看起来像这样:

/* Example.hpp */
struct Foo {
    int value;
    ~Foo();
};

struct Bar {
    Foo theFoo;
    Bar();
};

/* Example.cpp */
#include "Example.hpp"
Bar::Bar()  {theFoo.value=1;}
Foo::~Foo() {value=0;}

/* Example.i */
%module Example
%{
#include "Example.hpp"
%}
%include "Example.hpp"
Run Code Online (Sandbox Code Playgroud)

我在.i文件上运行SWIG(1.3.37),然后在Python中运行:

Python 2.4.3 (#1, Sept 17 2008, 16:07:08)
[GCC 4.1.2 20071124 (Red Hat 4.1.2-41)] on linux2
Type "help", "copyright", "credits", or "license" for more information.
>>> from Example import Bar
>>> b=Bar()
>>> print b.theFoo.value      # expect '1', since Bar's constructor sets this
1
>>> print Bar().theFoo.value  # expect '1', since we're still using the Foo object
26403424
Run Code Online (Sandbox Code Playgroud)

看来,在第二种情况下,临时Bar之前,我们永远能读取对象被销毁theFoovalue领域.在gdb中追逐事物,这显然正在发生的事情.因此,到我们读取.valueBar().theFoo,C++已经销毁(并用其他一些堆分配覆盖).theFoo.在我的实际情况中,这导致了段错误.

是否有任何SWIG指令或技巧可以添加到我的Example.i文件中以便Bar().theFoo.value返回1此处?

sen*_*rle 2

第二次更新

我们知道基本问题是 python 会Bar立即销毁。当Bar在python中实现时,python的gc知道仍然存在对 的引用theFoo,因此不会销毁它。但是当Bar用c++实现时,python调用c++析构函数,它会自动theFoo销毁Bar.

所以显而易见的解决方案是防止 pythonBar过早销毁。这是一个涉及子类化的稍微有点黑客的解决方案Bar

class PersistentBar(swigexample.Bar):
    lastpbar = None
    def __init__(self):
        super(PersistentBar, self).__init__()
        PersistentBar.lastpbar = self
Run Code Online (Sandbox Code Playgroud)

这会保存对最后创建的引用Bar,以便它不会立即被销毁。当创建新的时Bar,旧的将被删除。(我的旧版本很愚蠢;不需要__del__为此重写。)这是输出(带有cout << "deleting Foo "inFoo的析构函数):

>>> from test import persistentBar
>>> persistentBar().theFoo.value
1
>>> persistentBar().theFoo.value
deleting Foo 1
>>> persistentBar().theFoo.value
deleting Foo 1
Run Code Online (Sandbox Code Playgroud)

我还是不喜欢这个。将“持久”行为隔离在装饰器中可能会更好;我也尝试过并且有效(如果您想查看代码,请告诉我)。以某种方式告诉 python 处理自我销毁肯定会更好theFoo,但我不知道如何做到这一点。

第一次更新

包装代码没有告诉我什么,所以我查看了 swigexample.py。那也一无所获。当我尝试Bar用纯 python 进行复制时,事情变得更加清晰:

# pyfoobar.py
class Foo(object):
    def __init__(self):
        self.value = -1

class Bar(object):
    def __init__(self):
        self.theFoo = Foo()
        self.theFoo.value = 1
    def __del__(self):
        self.theFoo.value = 0
Run Code Online (Sandbox Code Playgroud)

现在我们从 pyfoobar 导入 Bar:

>>> from pyfoobar import Bar
>>> b = Bar()
>>> b.theFoo.value
1
>>> Bar().theFoo.value
0
Run Code Online (Sandbox Code Playgroud)

这种行为来自Python!

原答案

看起来这里肯定存在一些垃圾收集战斗......这是有关SWIG 内存管理的一些相关信息。基于此,看起来 %newobject 指令可能就是您正在寻找的;但我尝试了几种变体,但无法让 python 控制theFoo

>>> from swigexample import Bar
>>> b = Bar()
>>> b.theFoo.value
1
>>> b.theFoo.thisown
False
>>> Bar().theFoo.value
0
>>> Bar().theFoo.thisown
False
Run Code Online (Sandbox Code Playgroud)

我开始怀疑这是故意的;似乎上面链接中的这一行与这里相关:

C 现在持有该对象的引用——您可能不希望 Python 销毁它。

但我不确定。我将查看 swigexample_wrap 代码,看看是否可以弄清楚何时~Bar被调用。