Что вас в property смущает? На первый взгляд все хорошо.
А с методами - нет. Вернее, скорее всего генерируется не тот код, который вам нужен.
Поздравляю - вы добрались до call policies. Тема довольно непростая.
Когда метод (функция-член в терминологии С++) или функция верхнего уровня не имеют аргументов - то они их не имеют. Нет, соответственно, и call policy.
Если функция ничего не возвращает - она возвращает None (как и в pure python, между прочим). И указывать call policy тоже не нужно - boost.python сам разберется.
Идем дальше. Нужно понимать, что все преобразования типов делаются через to_python_converter/from_python_converter.
Это довольно сложная система правил, которая из статической информации о типе и call/return policies и динамическом знании о настоящем типе аргумента выбирает подходящий преобразователь.
Все очень гибко настраивается и расширяется - да только не всегда понятно изначально, в какую сторону следует копать.
Для примитивных типов (числа, строки и проч. неизменяемые объекты) - все хорошо и просто.
Можно позволить себе при пересечении барьера между яыками (не важно, в какую сторону) создавать копию.
Число - оно везде число, и подвоха от него не ждут. Думаю, все понятно.
Практически то же самое для простых классов на С++ - нужно определить конструктор копированя/оператор присваивания - и все работает.
Возможно, потом потребуется оптимизация - но это уже следующий шаг.
Делаем систему простой и рабочей, а потом оптимизируем.
Кстати, Гвидо в
http://python-history.blogspot.com дает немало интересных примеров в пользу такого подхода - и приводит случаи, когда его попытки преждевременной оптимизации вели к ошибкам в архитектуре языка, искорененным лишь в Python 3 (и то не до конца).
Сложности начинаются с изменяемыми объектами, да еще и завязанными в различные структуры данных типа “граф”.
Увидели указатель - повод насторожиться.
Есть еще некоторое количество промежуточных случаев, но сейчас речь о них не пойдет.
Главная проблема: Питон умеет считать ссылки для своих объектов, автоматически удаляя ненужные - а С/С++ нет.
Берем ваш пример: вызов Item.setOwner(WorldObject* value) из Питона.
Ключевые вопросы: кто создает value, кто им владеет и кто удаляет.
Поясню: если value создается С++ и удаляется им же - то как Питон узнает, что в его item owner уже не существует?
Или другой случай: мы удаляем item. value тоже должно быть удалено, или его “держит” что-то на стороне С++?
call policies помогают прописать правила для каждого конкретного случая, но решает все же программист.
Можно строить разные системы соглашений. Мой опыт говорит, что чем система проще и единообразней - тем она надежней.
Ошибки стоят дорого: в лучшем случае это необъяснимое падение программы по general protection fault.
Как правило, с поврежденным стеком - и даже postmortem debugging не может помочь. Объекты были повреждены задолго до вызова, “положившего” систему.
Может быть и гораздо хуже: повреждается “чужая” память, а программа продолжает работать. Выдавая совсем уж “чудесные” результаты.
Ловля нарушителя может занять месяцы - без гарантированного результата.
В разное время я использовал два решения для этой проблемы.
Первое - очень простое. С++ и Питон равноправны. Чтобы заставить их разделять владение объектом и работать совместно - нужно избавиться от простых указателей.
Вместо них следует применять boost::shared_ptr. При создании оберток использует shared_ptr в качестве HeldType template argument.
Т.е. код выглядит так:
class Item : public WorldObject {
private:
boost::shared_ptr<WorldObject> m_owner;
public:
boost::shared_ptr<WorldObject> getOwner() { return m_owner; };
void setOwner(boost::shared_ptr<WorldObject> value) { m_owner = value; };
};
class_<Item, bases<WorldObject>, boost::shared_ptr<WorldObject>, boost::noncopyable>("Item")
.add_property("owner", &Item::getOwner, &Item::setOwner)
.def("getOwner", &Item::getOwner, return_internal_reference<>())
.def("setOwner", &Item::setOwner);
Скорее всего, все будет немного сложнее и чтобы избежать “колец” прийдется в Item иметь m_owner как boost::weak_ptr<WorldObject> с соответствующими приведениями - но это уже очевидные детали.
Достоинства очевидны - все очень просто.
Недостатки тоже есть:
- переработка исходного кода (ее так или иначе трудно избежать)
- дополнительный уровень связывания (небольшие тормоза из-за лишнего разименовывания указателя)
- HeldType может быть только один (хотя никто не навязывает именно shared_ptr, можно использовать любой класс, который удовлетворяет контракту).
- иногда все же на стороне С++ нужен boost::python::object, который из shared_ptr получить нельзя.
- есть и другие, не столь очевидные сложности.
Но в целом подход очень рабочий.
Вариант номер два более замысловат.
Отделяем bp::object от того класса, который он на самом деле описывает. Храним в себе именно bp::object для того, чтобы Питон считал ссылки за нас.
Создаем разделяемые объекты только через Питоновский интерфейс - или из питона напрямую, или вызывая нашу обертку
PythonItem = class_<Item, bases<WorldObject>("Item") ...;
bp::object item = PythonItem();
Сам класс выглядит как
class Item : public WorldObject {
private:
bp::object py_owner;
WorldObject* m_owner;
public:
bp::object getOwner() { return py_owner; };
void setOwner(bp::object value) {
bp::extract<WorldObject*> extractor = bp::extract<WorldObject*>(value);
if(!extractor.check())
// выбросить исключение
py_owner = value;
m_owner = extractor();
};
};
Получаем “простые указатели” для С++ и одновременно поддержку Питоновской модели памяти.
Можно сделать ItemHolder, унаследовать его от Item и вставить всю Питоновскую специфику в него. Использовать ItemHoder как HeldType.
Можно придумать что-нибудь еще - но следует понимать главную проблему: простые указатели не умеют разделять владение и не содержат счетчика ссылок.
Поэтому приходится создавать некий механизм для этого - или как соглашение, или в явно использовать на стороне С++ “умные указатели” в любом виде.
P.S. Если что непонятно - спрашивайте.