Время жизни временных объектов в оболочках SWIG's Python (?) - PullRequest
8 голосов
/ 12 февраля 2011

Отредактировано 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"

Я запускаю SWIG (1.3.37) для файла .i, а затем в 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 уничтожается, прежде чем мы сможем прочитать поле theFoo value.Погоня за вещами в GDB, это ясно, что происходит.И поэтому к тому времени, когда мы читаем .value из Bar().theFoo, C ++ уже уничтожил (и переписал с каким-то другим выделением кучи) .theFoo.В моей реальной ситуации это вызывает ошибку segfault.

Есть ли какая-либо директива или хитрость SWIG, которую я могу добавить в свой файл Example.i, чтобы Bar().theFoo.value вернуть 1 здесь?

Ответы [ 2 ]

2 голосов
/ 12 февраля 2011

Второе обновление :

Хорошо, мы знаем, что основная проблема в том, что python уничтожает Bar немедленно. Когда Bar реализован в python, gc python знает, что есть ссылка на theFoo, и поэтому не уничтожает его. Но когда Bar реализован в C ++, Python вызывает деструктор C ++, который автоматически уничтожает theFoo вместе с Bar.

Таким образом, очевидное решение состоит в том, чтобы предотвратить преждевременное уничтожение Python Bar. Вот немного хакерское решение, включающее подклассы Bar:

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

Сохраняет ссылку на последний Bar, созданный таким образом, что он не будет сразу уничтожен. Когда создается новый Bar, старый удаляется. (Моя старая версия была глупой; для этого не нужно переопределять __del__.) Вот вывод (с cout << "deleting Foo " в деструкторе 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. Это также ничего не дало. Ситуация прояснилась, когда я попытался продублировать 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

Теперь мы импортируем Bar из pyfoobar:

>>> 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 теперь содержит ссылку на объект --- вы, вероятно, не хотите Питон, чтобы уничтожить его.

Но я не уверен. Я собираюсь посмотреть на код swigexample_wrap, чтобы узнать, смогу ли я выяснить, когда вызывается ~Bar.

0 голосов
/ 31 августа 2018

Решение состоит в том, чтобы добавить% naturalvar в ваш файл .i следующим образом:

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

Это заставляет SWIG возвращать копию Foo вместо ссылки на него, что решает проблему агрессивной очистки временных объектов, которую делает Python.

...