programacion - Fugas de memoria SWIG y C++ con un vector de punteros
punteros y matrices en c (1)
La advertencia que ve no está directamente relacionada con el hecho de que tiene un vector de punteros. Considere el siguiente archivo de interfaz 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; }
%}
El uso de esta interfaz proporciona:
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.
El problema es que SWIG solo ha visto una declaración, no una definición para struct foo
. El comportamiento predeterminado es que el objeto proxy de Python libere / elimine (según corresponda) el objeto subyacente aquí, pero no puede deducir cómo hacerlo basándose únicamente en la declaración de reenvío que ha visto.
Si extendemos el caso de prueba para incluir std::vector<foo>
se observa lo mismo:
%module test
%{
struct foo {};
%}
struct foo;
%include <std_vector.i>
%inline %{
foo bar() { return foo(); }
std::vector<foo> bar2() {
return std::vector<foo>();
}
%}
Lo cual nuevamente da la advertencia sobre no destructor:
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.
Sin embargo, podemos solucionarlo trivialmente asegurándonos de que haya una definición del tipo disponible. Para struct foo
eso es simplemente hacer que todo el cuerpo de la estructura sea visible para SWIG. Para std::vector<T>
necesitamos usar %template
para hacer eso:
%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>;
Que ahora no advierte (o se escapa para el caso):
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> >
>>>
La complicación es que en su ejemplo tiene std::vector<T*>
, por lo que podemos modificar nuestro caso de prueba para ilustrar eso:
%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*>;
Que luego podemos ejecutar:
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> >
>>>
Esto tiene una fuga, pero no muestra la advertencia que advertiste, porque en lo que respecta a SWIG, el std::vector
se ha eliminado correctamente (la misma semántica que en C ++).
En cuanto a cómo lidiar con la fuga, las opciones son las mismas que las habituales en C ++. Personalmente, trataría de evitar poner punteros crudos en un vector a menos que realmente quieras que los objetos apuntados sobrevivan al vector. Básicamente puedes:
- No almacena punteros en la estructura
- Utilice punteros inteligentes (
std::shared_ptr
ostd::unique_ptr
o aumente los equivalentes en su lugar). - Administre la memoria de forma manual de alguna manera.
Ya hemos hecho 1 en el segundo ejemplo. Con SWIG 2 es bastante simple también y 3 es una cuestión de escribir y ajustar otra función en su interfaz.
%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>
>>>
Lo que funciona, almacena punteros compartidos y no tiene fugas.
Si realmente quieres hacerlo de la tercera manera (lo evitaría a toda costa, ya que deja tu interfaz abierta a errores humanos), la forma más fácil de hacerlo con SWIG es usar %extend
, por ejemplo:
%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();
}
}
Lo que podemos hacer:
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
>>>
O podría usar %pythoncode
para modificar __del__
para llamar a la función automáticamente, pero sería una mala idea porque no afectaría a los objetos que Python nunca ve en absoluto y podría conducir a un comportamiento inesperado en algunos casos.
Estoy usando SWIG para la interfaz entre C ++ y Python. Creé una función que crea un std :: vector de punteros a objetos. Los objetos que se apuntan no son importantes en este caso.
El problema que tengo es que cuando el objeto ( someObject
) sale del alcance en el lado de Python, no puede liberar la memoria apuntada por los punteros del objeto dentro del vector, causando una pérdida de memoria.
Ejemplo
Código C ++:
std::vector < someObject* > createSomeObjectForPython() { std::vector < someObject* > myVector; someObject* instanceOfSomeObject = new someObject(); myVector.push_back(instanceOfSomeObject); return myVector; }
Del intérprete de Python:
objectVar = createSomeObjectForPython()
Cuando ejecuto esto en Python obtengo este error:
swig/python detected a memory leak of type ''std::vector< someObject *,std::allocator< someObject * > > *'', no destructor found.
Este error se debe a que cuando Python elimina el vector, solo puede eliminar los punteros dentro del vector y no realmente a lo que apuntan.
Si pudiera crear un destructor para std :: vector, esta sería la respuesta, pero no es posible.
Realmente necesito usar vectores de punteros opuestos a vectores de objetos antes de que alguien sugiera esto como una solución, particularmente porque los objetos son grandes y complejos, y la velocidad es un problema.
Estoy usando gcc4.4, swigwin 2.0.4 y Python 2.7 en Windows.