在Python中公开STL结构没有内存泄漏

时间:2015-01-13 18:34:29

标签: python c++ stl swig python-3.4

由于Flexo对SWIG and C++ memory leak with vector of pointers的回复,我已经绑定了Python std::vector<CameraT>。但我需要访问像CameraT.t这样的字段,我找不到合适的方法来做到这一点。

它在第三方头文件(DataInterface.h)中定义为:

template<class FT>
struct CameraT_ {
    typedef FT float_t;
    float_t t[3];
    /* more elaborate c++ stuff */
};
typedef CameraT_<float> CameraT;

接口文件看起来大致如下:

%module nvm
%{
    #define SWIG_FILE_WITH_INIT
    #include "util.h"
%}

%include "std_vector.i"

%inline %{
bool CameraDataFromNVM(const char* name, std::vector<CameraT>& camera_data){
    /* wrapper code */
}
%}

%template(CamVector) std::vector<CameraT>;

整合测试:

import nvm

if __name__ == "__main__":
    datafile = '../../example-data/vidstills.nvm'
    cams = nvm.CamVector()

    assert nvm.CameraDataFromNVM(datafile, cams)

    print(cams[0])  # this yields no memory warnings
    print(cams[0].t[0])  # 'SwigPyObject' object has no attribute 't'

    # Yields: swig/python detected a memory leak of type 'CameraT *', no destructor found.
    c = list([i for i in cams])

打印出来:

$ python3 test_nvm.py |& head
<Swig Object of type 'CameraT *' at 0x7f638228ee40>
Loading cameras/points: ../../example-data/vidstills.nvm
329 cameras; 8714 3D points; 74316 projections
Traceback (most recent call last):
  File "test_nvm.py", line 20, in <module>
    print(cams[0].t[0])
AttributeError: 'SwigPyObject' object has no attribute 't'
swig/python detected a memory leak of type 'CameraT *', no destructor found.

我尝试将struct CameraT {}#include语句放在%inline 块之内和之外并且失败了。循环遍历向量元素也会产生内存泄漏警告。不知道还能做什么。

代码位于Github

1 个答案:

答案 0 :(得分:1)

这里有几个问题。首先,要修复%include "datainterface.h"所需的内存泄漏警告,然后使用%template模板的另一个CameraT_指令。 (typedef不会改变这是必需的事实。)

所以:

%include "datainterface.h"
%template(CameraT) CameraT_<float>;

警告消失,我们可以访问该类型的成员。 (你提到的第一行没有警告,我认为这是Python的引用计数系统的一个怪癖)。

但并非如此,我们现在收到关于t无法下载的错误。我们可以通过使用-debug-tmsearch作为额外参数调用swig来获得一些信息,该参数显示正在选择的类型映射。至关重要的是,我们看到的是:

datainterface.h:4: Searching for a suitable 'ret' typemap for: CameraT_< float >::float_t CameraT_< float >::t[3]
[snip lots of tries...]
Looking for: SWIGTYPE
None found

稍微令人惊讶的是,在任何SWIG标题中都找不到任何合适的类型图。 (它确实存在于Java中,但在arrays_java.i中)。有几种方法可以解决它:

  1. 使用carrays.i并让用户做一些工作。
  2. 切换到包含现有包装器的容器。 (虽然不是第三方代码的选项)
  3. 自己写一些类型图。
  4. 由于1和2是微不足道的并且不是很好的Python我会跳过这些并为我们写一个或两个类型图,我的最终nvm.i文件最终看起来像:

    %module nvm
    %{
    #include "datainterface.h"
    %}
    
    %include "std_vector.i"
    
    %typemap(out) float[ANY] %{
      $result = PyTuple_New($1_dim0);
      for (unsigned i = 0; i < $1_dim0; ++i)
        PyTuple_SetItem($result,i,PyFloat_FromDouble($1[i])); 
    %}
    
    %typemap(in) float[ANY] (unsigned i=0, float tmp[$1_dim0]) %{
      $1 = tmp;
      PyObject *item, *iterator;
      iterator = PyObject_GetIter($input); // spliting this lets a macro work
    
      while (!PyErr_Occurred() && iterator &&
             i < $1_dim0 && (item = PyIter_Next(iterator))) {
        $1[i++] = PyFloat_AsDouble(item);
        Py_DECREF(item);
      }
    
      Py_DECREF(iterator);
    
      if (i != $1_dim0) {
        PyErr_SetString(PyExc_AttributeError, "Failed to get $1_dim0 floats" );
        SWIG_fail;
      }
    %}
    
    %include "datainterface.h"
    
    %inline %{
    bool CameraDataFromNVM(const char* name, std::vector<CameraT>& camera_data){
      return true;
    }
    %}
    
    %template(CameraT) CameraT_<float>;
    %template(CamVector) std::vector<CameraT>;
    

    让我运行您的测试文件并添加我的附件:

    import nvm
    
    if __name__ == "__main__":
        datafile = '../../example-data/vidstills.nvm'
        cams = nvm.CamVector(1)
    
        assert nvm.CameraDataFromNVM(datafile, cams)
    
        print(cams[0])  # this yields no memory warnings
        cams[0].t = (1,2,3)
        print(cams[0].t[0])
        #print(cams[0].bar)
    
        # Yields: swig/python detected a memory leak of type 'CameraT *', no destructor found.
        c = list([i for i in cams])
        print(c)
    

    这个解决方案有一个警告,它可以解决(通过返回代理对象)但不会自动成为交易破坏者:

    cams[0].t[0] = 1 
    print(cams[0].t[0]) # Won't actually have changed
    

    这是因为我们返回了一个新的Python元组,其中包含t的副本,因此这里的第二个下标运算符指的是原始C ++数据而不是原始C ++数据。如果你想解决这个问题,那么out typemap必须返回一个代理,其实现为__getitem____setitem__,而不仅仅是一个元组。根据您希望如何使用代码,可以实现速度/复杂性的折衷。我有an example of something similar to this,您需要调整out typemap以创建wrapped_array并调整wrapped_array以保存指向真实数组的指针(以及对Python对象的引用,因此它不会被释放“订单错误。”