В этой главе мы рассмотрим Boost. Python питание функций более подробно. Мы увидим некоторые возможности, чтобы обезопасить функции C++ от потенциальных пазов, таких как данглинговые указатели и ссылки. Мы также увидим объекты, которые сделают еще проще для нас разоблачить функции C++, которые используют преимущества C++, такие как перегрузка и аргументы по умолчанию.
Но прежде чем вы это сделаете, вы можете захотеть запустить Python 2.2 или позже и ввести >> импортировать это
.
>>> import this
The Zen of Python, by Tim Peters
Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it
Although that way may not be obvious at first unless you're Dutch.
Now is better than never.
Although never is often better than right now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea -- let's do more of those!
В C++ мы часто имеем дело с аргументами и типами возврата, такими как указатели и ссылки. Такие примитивные типы довольно, ммм, низкий уровень, и они действительно мало говорят нам. По крайней мере, мы не знаем владельца указателя или объекта. Неудивительно, что такие языки, как Java и Python, никогда не имеют дело с такими низкоуровневыми объектами. В C++ обычно считается хорошей практикой использования умных указателей, которые точно описывают семантику владения. Тем не менее, даже хорошие интерфейсы C++ иногда используют необработанные ссылки и указатели, поэтому увеличивайте. Python должен иметь дело с ними. Для этого может понадобиться ваша помощь. Рассмотрим следующую функцию C++:
X& f(Y& y, Z* z);
Как библиотека должна обернуть эту функцию? Наивный подход создает объект Python X вокруг ссылки результатов. Эта стратегия может или не может сработать. Вот пример, где его не было
>>> x = f(y, z) # x refers to some C++ X
>>> del y
>>> x.some_method() # CRASH!
В чем проблема?
Что, если f() был реализован, как показано ниже:
X& f(Y& y, Z* z)
{
y.z = z;
return y.x;
}
Проблема в том, что срок службы результата X& привязан к жизни y, потому что f() возвращает ссылку на члена объекта y. Эта идиома не является редкостью и вполне приемлемой в контексте C++. Тем не менее, пользователи Python не должны иметь возможность обрезать систему только с помощью нашего интерфейса C++. В этом случае удаление y аннулирует ссылку на X. У нас есть дрянная ссылка.
Вот что происходит:
f
называется переходом в ссылке y
и указателем на z
- Ссылка на
y.x
возвращается
y
удаляется. x
- это дразнительная ссылка
x.some_method()
называется
- BOOM!
Мы могли бы скопировать результат в новый объект:
>>> f(y, z).set(42)
>>> y.x.get()
3.14
Это не наше намерение нашего интерфейса C++. Мы нарушили наше обещание, что интерфейс Python должен отражать интерфейс C++ как можно ближе.
Наши проблемы не заканчиваются там. Допустим Y осуществляется следующим образом:
struct Y
{
X x; Z* z;
int z_value() { return z->value(); }
};
Обратите внимание, что член данных z
удерживается классом Y с использованием необработанного указателя. Теперь у нас есть потенциальная проблема с расщеплением указателя внутри Y:
>>> x = f(y, z) # y refers to z
>>> del z # Kill the z object
>>> y.z_value() # CRASH!
Для справки, вот реализация f
снова:
X& f(Y& y, Z* z)
{
y.z = z;
return y.x;
}
Вот что происходит:
f
называется переходом в ссылке y
и указателем на z
- Указатель на
z
удерживается y
- Ссылка на
y.x
возвращается
z
удаляется. y.z
- это данглинг указатель
y.z_ value()
называется
z-> value()
называется
- BOOM!
Политики вызова могут быть использованы в таких ситуациях, как приведенный выше пример. В нашем примере return_internal_reference
и с_custodian_and_ward
являются нашими друзьями:
def("f", f,
return_internal_reference<1,
with_custodian_and_ward<1, 2> >());
Каковы параметры 1
и 2
, спросите вы?
return_internal_reference<1
Информы Подъем. Python, что первый аргумент, в нашем случае Y& y
, является владельцем возвращенной ссылки: X&
. «1
» просто указывает первый аргумент. Короче говоря: «возвратить внутреннюю ссылку X&
принадлежит 1-му аргументу Y& y
.
with_custodian_and_ward<1, 2>
Информы Подъем. Python, что срок действия аргумента, указанного ward (т.е. 2-й аргумент: Z* z
), зависит от срока действия аргумента, указанного хранителем (т.е. 1-й аргумент: Y& y
).
Важно также отметить, что мы определили две стратегии выше. Две или более политики могут быть составлены путем сцепления. Вот общий синтаксис:
policy1<args...,
policy2<args...,
policy3<args...> > >
Вот список предопределенных политик вызовов. Полную ссылку, детализирующую их, можно найти здесь.
- с_custodian_and_ward: Ties lifes of the arguments
- с_custodian_and_ward_postcall: Сроки жизни аргументов и результатов
- return_internal_reference: Ties life of one argument to that of result
- return_value_policy с T один из:
- reference_ existing_object: наивный (опасный) подход
- copy_const_reference: Boost.Python v1
- copy_non_const_reference:
- manage_new_object: Принять указатель и удерживать экземпляр
Ниже показана схема для ручной обертывания перегруженных функций. Конечно, один и тот же метод может быть применен для обертывания перегруженных нечленов функций.
У нас есть наш класс C++:
struct X
{
bool f(int a)
{
return true;
}
bool f(int a, double b)
{
return true;
}
bool f(int a, double b, char c)
{
return true;
}
int f(int a, int b, int c)
{
return a + b + c;
};
};
Класс X имеет 4 перегруженных функции. Мы начнем с введения некоторых переменных указателей функций членов:
bool (X::*fx1)(int) = &X::f;
bool (X::*fx2)(int, double) = &X::f;
bool (X::*fx3)(int, double, char)= &X::f;
int (X::*fx4)(int, int, int) = &X::f;
С помощью них мы можем приступить к определению и обертке этого для Python:
.def("f", fx1)
.def("f", fx2)
.def("f", fx3)
.def("f", fx4)
Подъем. Python обертывает (член) указатели функций. К сожалению, у указателей функции C++ нет информации по умолчанию. Возьмите функцию f
с аргументами по умолчанию:
int f(int, double = 3.14, char const* = "hello");
Но тип указателя на функцию f
не имеет информации о своих аргументах по умолчанию:
int(*g)(int,double,char const*) = f;
Когда мы передаем эту функцию указателя на функцию def
, нет способа извлечь аргументы по умолчанию:
def("f", f);
Из-за этого, при обертке кода C++, нам пришлось прибегнуть к ручной обертке, как указано в предыдущем разделе, или писать тонкие обертывания:
int f1(int x) { return f(x); }
int f2(int x, double y) { return f(x,y); }
def("f", f);
def("f", f2);
def("f", f1);
Когда вы хотите обернуть функции (или функции-члены), которые либо:
- иметь аргументы по умолчанию или
- перегружены общей последовательностью первоначальных аргументов
Подъем. Теперь у Python есть способ сделать его проще. Например, с учетом функции:
int foo(int a, char b = 1, unsigned c = 2, double d = 3)
{
}
Название макро:
BOOST_PYTHON_FUNCTION_OVERLOADS(foo_overloads, foo, 1, 4)
автоматически создаст тонкие обертывания для нас. Этот макрос создаст класс foo_overloads
, который может быть передан на def(...)
. Третий и четвертый макро аргумент являются минимальными аргументами и максимальными аргументами соответственно. В нашей функции foo
минимальное количество аргументов составляет 1, а максимальное количество аргументов - 4. Функция def(...)
автоматически добавляет для нас все варианты foo:
def("foo", foo, foo_overloads());
Объекты здесь, объекты там, объекты здесь везде. Чаще всего нам нужно разоблачить функции членов наших классов на Python. Опять же, у нас те же неудобства, что и раньше, когда вступают в игру аргументы по умолчанию или перегрузка с общей последовательностью первоначальных аргументов. Еще один макрос предоставляется, чтобы сделать это бризом.
Например BOOST_PYTHON_FUNCTION_OVERLOADS
, BOOST_PYTHON_MEMBER_FUNCTION_OVERLOADS
может использоваться для автоматического создания тонких оберток для обертывания функций члена. Давайте возьмем пример:
struct george
{
void
wack_em(int a, int b = 0, char c = 'x')
{
}
};
Название макро:
BOOST_PYTHON_MEMBER_FUNCTION_OVERLOADS(george_overloads, wack_em, 1, 3)
будет генерировать набор тонких оберток для функции георге wack_em
, принимая минимум 1 и максимум 3 аргументов (т.е. третий и четвертый макро аргумент). Тонкие обертывания все заключены в класс под названием george_overloads
, который затем может быть использован в качестве аргумента для def(...)
:
.def("wack_em", &george::wack_em, george_overloads());
См. ссылку перегрузка для деталей.
Аналогичный объект предоставляется для классовых конструкторов, опять же, с аргументами по умолчанию или последовательностью перегрузок. Помните init<...>
? Например, учитывая класс X с конструктором:
struct X
{
X(int a, char b = 'D', std::string c = "constructor", double d = 0.0);
}
Вы можете легко добавить этого конструктора в Boost. Python в одном выстреле:
.def(init<int, optional<char, std::string, double> >())
Обратите внимание на использование init<...>
и ...>
для обозначения по умолчанию (факультативные аргументы).
В предыдущем разделе упоминалось, что BOOST_PYTHON_FUNCTION_OVERLOADS
и BOOST_PYTHON_MEMBER_FUNCTION_OVERLOADS
может также использоваться для перегрузки функций и функций-членов с общей последовательностью первоначальных аргументов. Вот пример:
void foo()
{
}
void foo(bool a)
{
}
void foo(bool a, int b)
{
}
void foo(bool a, int b, char c)
{
}
Как и в предыдущем разделе, мы можем генерировать тонкие обертывания для этих перегруженных функций одним выстрелом:
BOOST_PYTHON_FUNCTION_OVERLOADS(foo_overloads, foo, 0, 3)
Тогда...
.def("foo", (void(*)(bool, int, char))0, foo_overloads());
Заметьте, что сейчас у нас есть ситуация, когда у нас есть как минимум нулевые (0) аргументы и максимум три аргумента.
Однако важно подчеркнуть, что перегруженные функции должны иметь общую последовательность первоначальных аргументов. В противном случае наша схема выше не будет работать. Если это не так, мы должны обернуть наши функции manually.
На самом деле мы можем смешивать и сопоставлять ручную обертку перегруженных функций и автоматическую обертку через BOOST_PYTHON_MEMBER_FUNCTION_OVERLOADS
и его сестру, BOOST_PYTHON_FUNCTION_OVERLOADS
. Следуя нашему примеру, представленному в разделе на перегрузке, поскольку первые 4 фанкеты перегрузки имеют общую последовательность первоначальных аргументов, мы можем использовать BOOST_PYTHON_MEMBER_FUNCTION_OVERLOADS
, чтобы автоматически обернуть первые три из def
и вручную обернуть только последний. Вот как мы это сделаем:
BOOST_PYTHON_MEMBER_FUNCTION_OVERLOADS(xf_overloads, f, 1, 4)
Создание указателей функции члена как выше для обоих перегрузок X::f:
bool (X::*fx1)(int, double, char) = &X::f;
int (X::*fx2)(int, int, int) = &X::f;
Тогда...
.def("f", fx1, xf_overloads());
.def("f", fx2)