在 Python 中过早销毁的 SWIG 数组/类

Posted

技术标签:

【中文标题】在 Python 中过早销毁的 SWIG 数组/类【英文标题】:SWIG carrays/classes prematurely destroyed in Python 【发布时间】:2018-05-09 05:39:36 【问题描述】:

我正在尝试使用我认为在 C/C++ 中相当常见的结构,类似于以下内容:

// data.hpp
class Element 
  public:  
    int value;
    ~Element()  std::cout << "In node destructor" << std::endl; 
;

class Row 
  public: 
    Row(Element *elements) /*initialize elements, assign ptrs*/; 
    std::vector<Element *> elements;
;

class Dataset 
  public:  
    Dataset(Row *rows) /*initialize rows, assign ptrs*/;
    std::vector<Row *> rows;
;

存储指针,因为这实际上是在 CPU 和 GPU (CUDA) 上都使用的,我只想存储指针,以便每个设备可以自己找出对象的实际位置。

我的 SWIG 映射非常基础:

/* File : data.i */
%
#include "data.hpp"
%

%include carrays.i
%include "data.hpp"

%array_class(Node, NodeArray)
%array_class(Row, RowArray)

现在我需要将 Python/Numpy 数组转换为行数组,以便将它们传递给 Dataset 构造函数。认为这样的事情可能会起作用:

def array_to_rows(X):
    nr_rows = np.shape(X)[0]
    c_row_arr = example.RowArray(nr_nodes)
    for r in range(nr_rows):
        nr_nodes = len(X[r])
        c_node_arr = example.NodeArray(nr_nodes)
        for n in range(nr_nodes):
            node = example.Node()
            node.value = int(X[r][n])
            c_node_arr[n] = node // <-- after this line node's destructor is called
        c_row_arr[r] = example.Row(node_arr) // <-- after this line row's destructor is called and destructor for each Node in c_node_arr
    return c_row_arr

示例调用:

import example as example
X = [
     [1],
     [2,3],
     [4,5,6]
     ]
rows = array_to_rows(X)

问题是,在 Python 中的每个循环结束时,都会调用 NodeRow 的析构函数。所以即使我做了c_node_arr[n] = node,这个赋值也不会让Python坚持node而是删除它......

我假设这是因为 SWIG 数组正在使用指针,如果我这样做 c_node_arr[n] = node 它只会将指针设置为 node,然后将在循环结束时由 Python 释放(以及C++ 析构函数将被调用),c_node_arr 将被挂起,并带有一个指向已被释放的内存位置的指针。

有什么解决方法吗?我的方法是不是很糟糕,我应该重新考虑(如何?)。

@编辑:

目前我看到的唯一解决方法是:

1) 将 RowArrayNodeArray 的所有实例也保存在 Python 列表中,并在完成后释放它们

2) 将分配给RowArrayNodeArray= 更改为__setitem(idx, value)

【问题讨论】:

【参考方案1】:

我认为 SWIG 正在触发您的 C++ 对象的许多副本。我认为它不会留下任何悬空指针。

来自http://www.swig.org/Doc1.3/Library.html%array_class(type,name) 是:

struct name 
  ...
  void setitem(int index, type value);  // Set item

我认为c_node_arr[n] = node 会发生这种情况。

所以你实际上有:

    [Python]c_node_arr.__setitem__(n, node):将node 传递到 SWIG。 [SWIG] 提取底层Node*。 [SWIG] 致电name::setitem(..., *node)。 [C++] 作为参数传递的一部分,构造一个新的Node 对象作为参数的副本(如同Node new_node = node;)。尝试为 Element 编写一个复制构造函数,我想你会在这里看到一个调用。 [C++] 将其存储在struct name 的某个内部数组中。我想这第二个副本不知何故被省略了。 ...稍后... [Python]决定删除原来的node对象。这是您看到析构函数运行的地方。在内部,NodeArray 指向(并拥有)原始节点的副本。

如果ElementRow 可以被复制(使用默认或自定义复制构造函数),那么一切都很好。

【讨论】:

以上是关于在 Python 中过早销毁的 SWIG 数组/类的主要内容,如果未能解决你的问题,请参考以下文章

从 swig 返回 double * 作为 python 列表

无法使用 SWIG 在 Python 中实例化 C++ 类(获取属性错误)

如何在 SWIG/Python 中将结构列表传递给 C

python的swig typemap:输入和输出数组

函数之间的数组指针丢失值(用 swig 编译以在 python3 中使用)

将派生类传递给采用 SWIG Python 中的基类的函数