Существует несколько разных способов решения этой проблемы, поэтому я постараюсь объяснить их по очереди, опираясь на несколько вещей по пути. Надеемся, что это полезно для просмотра параметров и особенностей SWIG, даже если вам действительно нужен только первый пример.
Добавить код Python для непосредственного изменения thisown
Решение, наиболее похожее на предложенное вами, основано на использовании директивы %pythonprepend
SWIG для добавления дополнительного кода Python. Вы можете настроить таргетинг на основе объявления C ++ о перегрузке, которая вас волнует, например ::10000
%module freemenot
%{ #include "freemenot.h" %}
%include "std_string.i"
%pythonprepend MyContainer::addObject(MyObject*) %{
# mess with thisown
print('thisown was: %d' % args[0].thisown)
args[0].thisown = 0
%}
//Expose to Python
%include "freemenot.h"
Там, где единственная примечательная особенность возникает из-за того, что аргументы передаются с использованием *args
вместо именованных аргументов, поэтому мы должны получить к нему доступ по номеру позиции.
Есть несколько других мест / методов для вставки дополнительного кода Python (при условии, что вы не используете -builtin
) в документации SWIG Python , и всегда можно использовать параметр «monkey patching».
Используйте Python C API для настройки thisown
Следующим возможным вариантом здесь является использование таблицы типов, вызывающей API-интерфейс Python C, для выполнения эквивалентных функций. В этом случае я соответствовал типу аргумента и имени аргумента, но это означает, что карта типов будет применена ко всем функциям, которые получают MyObject *
с именем o
. (Самое простое решение в этом случае - сделать так, чтобы имена описывали предполагаемую семантику в заголовках, если в настоящее время они будут соответствовать друг другу, что имеет побочное преимущество, заключающееся в упрощении IDE и документации).
%module freemenot
%{ #include "freemenot.h" %}
%include "std_string.i"
%typemap(in) MyObject *o {
PyObject_SetAttrString($input, "thisown", PyInt_FromLong(0)); // As above, but C API
$typemap(in,MyObject*); // use the default typemap
}
//Expose to Python
%include "freemenot.h"
Самым примечательным моментом в этом примере, отличном от сопоставления карты типов, является использование $typemap
здесь для «вставки» другой карты типов, в частности, значения по умолчанию для MyObject*
, в нашу собственную карту типов. Стоит взглянуть в сгенерированный файл оболочки на пример до / после того, как это выглядит в итоге.
Используйте среду выполнения SWIG для прямого доступа к SwigPyObject
элементу own
struct *
Поскольку мы уже пишем C ++ вместо того, чтобы проходить через setattr
в коде Python, мы можем адаптировать эту карту типов для использования большего количества внутренних компонентов SWIG и пропустить переход от C к Python и обратно к C снова.
Внутри SWIG есть структура, которая содержит детали каждого экземпляра, включая владельца, тип и т. Д.
Мы могли бы просто привести непосредственно от PyObject*
к SwigPyObject*
, но для этого потребовалось бы написать обработку ошибок / проверку типа (это PyObject, даже SWIG?) И мы сами стали бы зависеть от деталей различных различных способов. SWIG может создавать интерфейсы Python. Вместо этого есть одна функция, которую мы можем вызвать, которая обрабатывает все это для нас, поэтому мы можем написать нашу карту типов следующим образом:
%module freemenot
%{ #include "freemenot.h" %}
%include "std_string.i"
%typemap(in) MyObject *o {
// TODO: handle NULL pointer still
SWIG_Python_GetSwigThis($input)->own = 0; // Safely cast $input from PyObject* to SwigPyObject*
$typemap(in,MyObject*); // use the default typemap
}
//Expose to Python
%include "freemenot.h"
Это всего лишь эволюция предыдущего ответа, но реализованная исключительно во время выполнения SWIG C.
Скопируйте конструкцию нового экземпляра перед добавлением
Существуют и другие способы решения этой проблемы собственности. Во-первых, в этом конкретном случае ваш MyContainer
предполагает, что он всегда может вызывать delete
для каждого экземпляра, который он хранит (и, следовательно, владеет этой семантикой).
Мотивирующим примером для этого было бы, если бы мы также обернули такую функцию:
MyObject *getInstanceOfThing() {
static MyObject a;
return &a;
}
Что приводит к проблеме с нашими предыдущими решениями - мы устанавливаем thisown
в 0, но здесь это уже было бы 0, и поэтому мы все еще не можем юридически вызвать delete
для указателя, когда контейнер освобожден.
Существует простой способ справиться с этим, который не требует знания внутренних компонентов прокси-сервера SWIG - при условии, что MyObject
является копируемым, тогда вы можете просто создать новый экземпляр и быть уверенным, что независимо от того, откуда он взялся, он собирается быть законным для контейнера, чтобы удалить его. Мы можем сделать это, немного адаптировав нашу карту типов:
%module freemenot
%{ #include "freemenot.h" %}
%include "std_string.i"
%typemap(in) MyObject *o {
$typemap(in,MyObject*); // use the default typemap as before
$1 = new $*1_type(*$1); // but afterwards call copy-ctor
}
//Expose to Python
%include "freemenot.h"
Обратите внимание на то, что здесь используется еще несколько функций SWIG, которые позволяют нам знать тип входных данных карты типов - $*1_type
- это тип аргумента карты типов, на который однажды ссылались. Мы могли бы просто написать MyObject
здесь, так как это то, к чему он относится, но это позволяет вам обрабатывать такие вещи, как шаблоны, если ваш контейнер действительно является шаблоном, или повторно использовать карту типов в других подобных контейнерах с %apply
.
Здесь нужно следить за утечками, если у вас была функция C ++, которую вы намеренно позволяли возвращать экземпляру без установки thisown
в предположении, что контейнер станет владельцем, который теперь не будет храниться.
Дайте контейнеру возможность управлять владельцем
Наконец, одна из других техник, которые мне нравятся, не так часто здесь возможна, как в настоящее время, но стоит упомянуть для потомков. Если у вас есть возможность хранить некоторые дополнительные данные рядом с каждым экземпляром в контейнере, вы можете вызвать Py_INCREF
и сохранить ссылку на базовый PyObject*
независимо от того, откуда он взялся. При условии, что вы получите обратный вызов во время уничтожения, вы также можете вызвать Py_DECREF
и заставить среду выполнения Python поддерживать объект живым, пока он находится в контейнере.
Вы также можете сделать это даже тогда, когда невозможно сохранить соединение 1-1 MyObject*
/ PyObject*
, сохранив теневой контейнер где-нибудь еще. Это может быть трудно сделать, если вы не хотите добавить другой объект в контейнер, создать его подкласс или не можете быть уверены, что начальный экземпляр Python контейнера всегда будет жить достаточно долго.