【问题标题】:Lifetime of temporary objects in SWIG's Python wrappers (?)SWIG 的 Python 包装器中临时对象的生命周期(?)
【发布时间】:2011-02-12 00:45:26
【问题描述】:

2 月 12 日编辑

我最近在使用 SWIG 生成的一些 C++ 类的 Python 包装器时遇到了一个奇怪的崩溃。看来 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"

我在 .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

似乎在第二种情况下,临时的Bar 对象在我们读取theFoovalue 字段之前就被销毁了。在 gdb 中追逐东西,这显然是正在发生的事情。因此,当我们从Bar().theFoo 中读取.value 时,C++ 已经销毁(并被其他一些堆分配覆盖).theFoo。在我的实际情况下,这会导致段错误。

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

【问题讨论】:

  • 如果我的回答解决了您的问题,或者您找到了什么解决方案,请告诉我——这是一个有趣的问题!
  • 遗憾的是,结论是“接受它”。没有好的解决方案出现 =(

标签: c++ python swig lifetime temporary-objects


【解决方案1】:

第二次更新

我们知道基本问题是 python 立即销毁Bar。当Bar在python中实现时,python的gc知道还有theFoo的引用,所以不会销毁它。但是当Bar用c++实现时,python调用了c++的析构函数,它会自动销毁theFooBar.

所以显而易见的解决方案是防止python过早地破坏Bar。这是一个涉及子类化Bar 的略显老套的解决方案:

class PersistentBar(swigexample.Bar):
    lastpbar = None
    def __init__(self):
        super(PersistentBar, self).__init__()
        PersistentBar.lastpbar = self

这会保存对最后创建的Bar 的引用,这样它就不会立即被销毁。创建新的Bar 时,旧的会被删除。 (我的旧版本很傻;不需要为此覆盖__del__。)这是输出(Foo 的析构函数中的cout << "deleting Foo "):

>>> from test import persistentBar
>>> persistentBar().theFoo.value
1
>>> persistentBar().theFoo.value
deleting Foo 1
>>> persistentBar().theFoo.value
deleting Foo 1

我还是不喜欢这个。在装饰器中隔离“持久”行为可能会更好;我也试过了,它奏效了(如果你想看代码,请告诉我)。以某种方式告诉 python 处理销毁theFoo 本身肯定会更好,但我不知道该怎么做。

首次更新

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

# 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

现在我们从 pyfoobar 导入 Bar:

>>> from pyfoobar import Bar
>>> b = Bar()
>>> b.theFoo.value
1
>>> Bar().theFoo.value
0

此行为来自 Python!

原答案

看来这里肯定有一些垃圾收集战斗在起作用...这是SWIG Memory Management 上的一些相关信息。基于此,看起来 %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

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

C 现在持有对 对象---你可能不想要 Python 来摧毁它。

但我不确定。我将查看 swigexample_wrap 代码,看看我是否能确定何时调用 ~Bar

【讨论】:

  • 当然是我提出的情况的一个很好的答案。不幸的是,它并没有解决我真正的问题。我想我需要修改一些我原来的问题。
  • 好的,更改问题以更好地适应实际问题。
  • 我还尝试按照您的示例用纯 python 编写更改后的示例。在纯 Python 中(即在 Foo 中使用 del 方法),Bar().theFoo.value == 1,正如预期的那样。
【解决方案2】:

解决方案是将 %naturalvar 添加到您的 .i 文件中,如下所示:

%naturalvar Bar::theFoo;
%include "Example.hpp"

这会导致 SWIG 返回 Foo 的副本而不是对其的引用,这解决了 Python 所做的激进的临时对象清理问题。

【讨论】:

    猜你喜欢
    • 2012-09-22
    • 2023-02-07
    • 1970-01-01
    • 2013-11-20
    • 2016-12-25
    • 2019-01-06
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多