SWIG носители / классы преждевременно уничтожены в Python - PullRequest
0 голосов
/ 09 мая 2018

Я пытаюсь работать с довольно распространенной структурой, я думаю, в 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 вызываются деструкторы как для Node, так и для Row. Поэтому, хотя я выполняю c_node_arr[n] = node, это назначение не заставляет Python удерживать node, а удаляет его ...

Я предполагаю, что это потому, что массивы SWIG работают с указателями, и если я сделаю c_node_arr[n] = node, он просто установит указатель на node, который затем будет освобожден Python в конце цикла (и Будет вызван деструктор C ++) и c_node_arr останется висеть с указателем на область памяти, которая уже была освобождена.

Есть ли обходной путь? Мой подход просто плохой, и я должен переосмыслить его (как?).

@ Edit:

На данный момент единственный обходной путь, который я вижу:

1) хранить все экземпляры RowArray и NodeArray также в списке Python и освобождать их, когда я закончу

2) изменить назначения на RowArray и NodeArray с = на __setitem(idx, value)

1 Ответ

0 голосов
/ 21 июля 2018

Я думаю, что 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.

Итак, у вас действительно есть:

  1. [Python] c_node_arr.__setitem__(n, node): передать node в SWIG.
  2. [SWIG] Извлечь базовый Node*.
  3. [SWIG] Звоните name::setitem(..., *node).
  4. [C ++] В рамках передачи параметров создайте новый объект Node как копию аргумента (как если бы Node new_node = node;). Попробуйте написать конструктор копирования для Element, я думаю, вы увидите здесь звонок.
  5. [C ++] Сохраните его во внутреннем массиве struct name. Я думаю, что эта вторая копия как-то исключена.
  6. ... позже ... [Python] Решает удалить исходный объект node. Здесь вы видите, как работает деструктор. Внутренне NodeArray указывает (и владеет) копией исходного узла.

Если Element, Row можно сделать копируемым (с конструкторами копирования по умолчанию или с пользовательскими настройками), то все в порядке.

...