Подтвердить что ты не робот

Утечка памяти SWIG и С++ с вектором указателей

Я использую SWIG для взаимодействия между С++ и Python. Я создал функцию, которая создает указатели объектов std::vector. Объекты, на которые указывают, в этом случае не важны.

Проблема заключается в том, что когда объект (someObject) выходит за пределы области действия на стороне Python, он не может освобождать память, на которую указывают указатели объекта/указателя внутри вектора, что вызывает утечку памяти.

Пример

  • Код С++:

    std::vector < someObject* > createSomeObjectForPython()
    {
       std::vector < someObject* > myVector;
       someObject* instanceOfSomeObject = new someObject();
       myVector.push_back(instanceOfSomeObject);
       return myVector;
    }
    
  • Из интерпретатора Python:

    objectVar = createSomeObjectForPython()
    

Когда я запускаю это в Python, я получаю эту ошибку:

swig/python detected a memory leak of type 'std::vector< someObject *,std::allocator<  someObject * > > *', no destructor found.

Эта ошибка возникает из-за того, что когда Python удаляет вектор, он может удалять указатели внутри вектора, а не то, на что они указывают.

Если бы я мог создать деструктор для std::vector, это был бы ответ, но это не возможно.

Мне действительно нужно использовать векторы указателей, противоположных векторам объектов, прежде чем кто-либо предложит это как решение, особенно потому, что объекты большие и сложные, а скорость - проблема.

Я использую gcc4.4, swigwin 2.0.4 и Python 2.7 для Windows.

4b9b3361

Ответ 1

Предупреждение, которое вы видите, прямо не связано с тем, что у вас есть вектор указателей. Рассмотрим следующий файл интерфейса SWIG:

%module test

// This just gets passed straight through and not used for wrapping
%{
struct foo {};
%}

struct foo;

%inline %{
  struct foo bar() { struct foo f; return f; }
%}

Использование этого интерфейса дает:

swig -Wall -python test.i && gcc -Wall -Wextra -std=c99 -shared -o _test.so test_wrap.c -I/usr/include/python2.7 && python2.7
Python 2.7.3 (default, Aug  1 2012, 05:16:07) 
[GCC 4.6.3] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import test
>>> test.bar()
<Swig Object of type 'struct foo *' at 0xb7654a70>
>>> 
swig/python detected a memory leak of type 'struct foo *', no destructor found.

Проблема заключается в том, что SWIG видел только объявление, а не определение для struct foo. Поведение по умолчанию заключается в том, что объект прокси-сервера Python может освобождать/удалять (в зависимости от ситуации) базовый объект здесь, но он не может определить, как это сделать, основываясь только на декларации, которую он видел.

Если мы расширим тестовый пример включением std::vector<foo>, то будет наблюдаться одно и то же:

%module test

%{
struct foo {};
%}

struct foo;

%include <std_vector.i>

%inline %{
  foo bar() { return foo(); }
  std::vector<foo> bar2() { 
    return std::vector<foo>(); 
  } 
%}

Что опять предупреждает об отсутствии деструктора:

Python 2.7.3 (default, Aug  1 2012, 05:16:07) 
[GCC 4.6.3] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import test
>>> print test.bar2()
<Swig Object of type 'std::vector< foo,std::allocator< foo > > *' at 0xb7671a70>swig/python detected a memory leak of type 'std::vector< foo,std::allocator< foo > > *', no destructor found.

Однако мы можем тривиально исправить это, убедившись, что доступно определение типа. Для struct foo, который просто делает весь объект структуры видимым для SWIG. Для std::vector<T> нам нужно использовать %template для этого:

%module test

%include <std_vector.i>

%inline %{
  struct foo {};
  foo bar() { return foo(); }
  std::vector<foo> bar2() { 
    return std::vector<foo>(); 
  } 
%}

%template(FooVec) std::vector<foo>;

Что теперь не предупреждает (или утечка в этом случае):

Python 2.7.3 (default, Aug  1 2012, 05:16:07) 
[GCC 4.6.3] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import test
>>> print test.bar()
<test.foo; proxy of <Swig Object of type 'foo *' at 0xb76aba70> >
>>> print test.bar2()
<test.FooVec; proxy of <Swig Object of type 'std::vector< foo > *' at 0xb76abab8> >
>>> 

Усложнение состоит в том, что в вашем примере у вас есть std::vector<T*>, поэтому мы можем изменить наш тестовый пример, чтобы проиллюстрировать это:

%module test

%include <std_vector.i>

%inline %{
  struct foo {};
  foo bar() { return foo(); }
  std::vector<foo*> bar2() { 
    return std::vector<foo*>(1, new foo); 
  } 
%}

%template(FooVec) std::vector<foo*>;

Что мы можем запустить:

Python 2.7.3 (default, Aug  1 2012, 05:16:07) 
[GCC 4.6.3] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import test
>>> print test.bar2()
<test.FooVec; proxy of <Swig Object of type 'std::vector< foo * > *' at 0xb7655a70> >
>>> 

Это утечка, но в решающей степени не отображается предупреждение, которое вы заметили, потому что, насколько это касается SWIG, сам std::vector был удален (точно такая же семантика, как и на С++).

Что касается того, как справляться с утечкой, то в С++ параметры такие же, как обычно. Лично я бы попытался избегать размещения необработанных указателей в векторе, если вы действительно не хотите, чтобы объекты указывали на то, чтобы пережить вектор. В основном вы можете:

  • Не хранить указатели в структуре
  • Используйте интеллектуальные указатели (std::shared_ptr или std::unique_ptr или более высокие эквиваленты).
  • Управляйте память вручную как-то.

Мы уже сделали 1 во втором примере. С SWIG 2 тоже довольно просто, а 3 - это вопрос написания и упаковки другой функции в вашем интерфейсе.

%module test

%include <std_vector.i>
%include <std_shared_ptr.i>

%{
#include <memory>
%}

%inline %{
  struct foo {};
  foo bar() { return foo(); }
  std::vector<std::shared_ptr<foo> > bar2() { 
    return std::vector<std::shared_ptr<foo> >(1, std::make_shared<foo>()); 
  } 
%}

%shared_ptr(Foo);
%template(FooVec) std::vector<std::shared_ptr<foo> >;
Python 2.7.3 (default, Aug  1 2012, 05:16:07) 
[GCC 4.6.3] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import test
>>> print test.bar2()
<test.FooVec; proxy of <Swig Object of type 'std::vector< std::shared_ptr< foo >,std::allocator< std::shared_ptr< foo > > > *' at 0xb76f4a70> >
>>> print test.bar2()[0]
<Swig Object of type 'std::vector< std::shared_ptr< foo > >::value_type *' at 0xb76f4a70>
>>> 

Что работает, сохраняет общие указатели и не течет.

Если вы действительно хотите сделать третий способ (я бы избегал его любой ценой, учитывая, что он оставляет ваш интерфейс открытым для человеческих ошибок), самый простой способ сделать это с помощью SWIG - использовать %extend, например:

%module test

%include <std_vector.i>

%inline %{
  struct foo {};
  foo bar() { return foo(); }
  std::vector<foo*> bar2() { 
    return std::vector<foo*>(1, new foo); 
  } 
%}

%template(FooVec) std::vector<foo*>;

%extend std::vector<foo*> {
  void empty_and_delete() {
    for (std::vector<foo*>::iterator it = $self->begin(); 
         it != $self->end(); ++it) {
      delete *it;
    }
    $self->clear();
  }
}

Мы можем сделать:

Python 2.7.3 (default, Aug  1 2012, 05:16:07) 
[GCC 4.6.3] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import test
>>> x = test.bar2()
>>> print x.size()
1
>>> x.empty_and_delete()
>>> print x.size()
0
>>> 

Или вы могли использовать %pythoncode для изменения __del__, чтобы вызвать функцию автоматически, но это было бы плохой идеей, потому что это не повлияло бы на объекты Python никогда не видит вообще и может привести к неожиданному поведению в нескольких случаях.