How to avoid memory leak with shared_ptr and SWIG

2019-04-06 10:36发布

问题:

I'm trying to use boost::shared_ptr's to allow for me to use c++ file I/O stream objects in my python script. However, the generated wrapper warns me that it is leaking memory.

Here's a minimal .i file exhibiting the problem:

%module ptrtest

%include "boost_shared_ptr.i"
%include "std_string.i"

%shared_ptr( std::ofstream )

%{
#include <fstream>
#include <boost/shared_ptr.hpp>

typedef boost::shared_ptr< std::ofstream > ofstream_ptr;

ofstream_ptr mk_out(const std::string& fname ){
    return ofstream_ptr( new std::ofstream( fname.c_str() ) );
}

%}

ofstream_ptr mk_out(const std::string& fname );


%pythoncode %{

def leak_memory():
    ''' demonstration function -- when I call
        this, I get a warning about memory leaks
    ''''
    ostr=mk_out('/tmp/dont_do_this.txt')


%}

Here's the warning:

In [2]: ptrtest.leak_memory()
swig/python detected a memory leak of type 'ofstream_ptr *', no destructor found.

Is there a way to modify the .i file to tell the interface how to dispose of the shared_ptr properly?

回答1:

Your example is missing two parts to get the destructor to run:

  1. Since SWIG knows absolutely nothing about std::ofstream the default behaviour is to do nothing beyond pass an opaque handle around. See another answer of mine for a further discussion of this.

    The fix here is to supply an empty definition for std::ofstream in your interface file to convince SWIG it knows enough to do more, even if you don't plan on exposing any members.

  2. SWIG needs to see the typedef itself - inside the %{ %} it just gets passed straight to the output module, not used in the wraping itself.

Thus your example becomes:

%module ptrtest

%include "boost_shared_ptr.i"
%include "std_string.i"

%shared_ptr( std::ofstream )

namespace std {
  class ofstream {
  };
}

%{
#include <fstream>
#include <boost/shared_ptr.hpp>

typedef boost::shared_ptr< std::ofstream > ofstream_ptr;

ofstream_ptr mk_out(const std::string& fname ){
    return ofstream_ptr( new std::ofstream( fname.c_str() ) );
}
%}

typedef boost::shared_ptr< std::ofstream > ofstream_ptr;
ofstream_ptr mk_out(const std::string& fname );

%pythoncode %{
def leak_memory():
    ostr=mk_out('/tmp/dont_do_this.txt')
%}

For future reference you can avoid duplication of stuff that lives only in the .i file with %inline:

%inline %{
typedef boost::shared_ptr< std::ofstream > ofstream_ptr;

ofstream_ptr mk_out(const std::string& fname ){
    return ofstream_ptr( new std::ofstream( fname.c_str() ) );
}
%}

Which declares, defines and wraps it all in one shot.