Fuite de mémoire de SWIG et C++ avec vecteur de pointeurs
J'utilise SWIG pour l'interface entre C++ et Python. J'ai créé une fonction qui crée un std::vector de pointeurs d'objet. Les objets pointés ne sont pas importants dans ce cas.
le problème que j'ai est que lorsque l'objet ( someObject
) sort de la portée du côté Python il ne peut pas libérer la mémoire pointée par les pointeurs objet/s dans le vecteur, provoquant ainsi une fuite de mémoire.
exemple
-
code C++:
std::vector < someObject* > createSomeObjectForPython() { std::vector < someObject* > myVector; someObject* instanceOfSomeObject = new someObject(); myVector.push_back(instanceOfSomeObject); return myVector; }
-
de L'interpréteur Python:
objectVar = createSomeObjectForPython()
quand j'exécute ceci en Python je reçois cette erreur:
swig/python detected a memory leak of type 'std::vector< someObject *,std::allocator< someObject * > > *', no destructor found.
cette erreur est due au fait que lorsque Python supprime le vecteur, il ne peut supprimer que les pointeurs à l'intérieur du vecteur et pas réellement ce qu'ils pointent.
si je pouvais créer un destructeur pour le std::vector, ce serait la réponse, mais il n'est pas possible.
j'ai vraiment besoin d'utiliser des vecteurs de pointeurs opposés à des vecteurs d'objets avant que quelqu'un ne suggère cette solution, en particulier parce que les objets sont grands et complexes, et la vitesse est un problème.
j'utilise gcc4.4, swigwin 2.0.4, et Python 2.7 sous Windows.
1 réponses
L'avertissement que vous voyez ne repose pas directement avec le fait que vous avez un vecteur de pointeurs. Considérez le fichier d'interface de SWIG suivant:
%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; }
%}
en utilisant cette interface donne:
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.
le problème est que SWIG a seulement vu une déclaration, pas une définition pour struct foo
. Le comportement par défaut est que L'objet Python proxy libère / supprime (selon le cas) l'objet sous-jacent ici, mais il n'est pas capable de déduire comment faire cela basé seulement sur la déclaration qu'il a vu.
si nous étendons le cas d'essai pour inclure std::vector<foo>
la même chose est observée:
%module test
%{
struct foo {};
%}
struct foo;
%include <std_vector.i>
%inline %{
foo bar() { return foo(); }
std::vector<foo> bar2() {
return std::vector<foo>();
}
%}
qui donne à nouveau l'avertissement qu'il n'y a pas de destructeur:
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.
Cependant, nous pouvons vaguement corriger cela en nous assurant qu'une définition du type est disponible. Pour struct foo
c'est tout simplement faire le corps entier de la structure visible pour SWIG. Pour std::vector<T>
nous devons utiliser %template
pour faire cela:
%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>;
qui ne prévient pas (ou fuient d'ailleurs):
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 complication est que dans votre exemple vous avez std::vector<T*>
, donc nous pouvons modifier notre cas d'essai pour illustrer que:
%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 nous pouvons alors exécuter:
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> >
>>>
Ce ne la fuite, mais n'est pas montre l'avertissement que vous avez remarqué, car en ce qui concerne SWIG le std::vector
lui-même a été correctement supprimé (la même sémantique que dans C++ en fait).
en ce qui concerne la façon de gérer la fuite, les options sont les mêmes que d'habitude en C++. Personnellement, j'essaierais de éviter de mettre des pointeurs bruts dans un vecteur sauf si vous voulez vraiment que les objets pointés pour survivre au vecteur. Fondamentalement, vous can:
- ne Pas stocker des pointeurs de la struct
- utilisez des pointeurs intelligents (
std::shared_ptr
oustd::unique_ptr
ou des équivalents de boost à la place). - gérer la mémoire manuellement.
nous avons déjà fait 1 dans le second exemple. Avec SWIG 2 est assez simple aussi bien et 3 est une question d'écrire et envelopper une autre fonction dans votre interface.
%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>
>>>
qui fonctionne, stocke des pointeurs partagés et ne fuit pas.
si vous voulez vraiment faire la troisième voie (je l'éviterais à tout prix étant donné qu'il laisse votre interface ouverte aux erreurs humaines) la meilleure façon de le faire avec SWIG est d'utiliser %extend
, par exemple:
%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();
}
}
L'on peut faire:
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
>>>
ou vous pouvez utiliser %pythoncode
pour modifier __del__
appeler la fonction automatiquement, mais ce serait une mauvaise idée car cela n'affecterait pas les objets que Python ne voit jamais et pourrait conduire à un comportement inattendu dans quelques cas.