Boost.Python - Как вернуться по ссылке?

Я использую Boost.Python для создания модулей Python из классов С++. И я столкнулся с проблемой со ссылками.

Разделите следующий случай, когда у меня есть класс Foo с перегруженными методами get, которые могут либо возвращаться по значению, либо по ссылке.

Задание того, что значение return by value должно использоваться, было легко, как только я набрал подпись. Но я думаю, что также возможно вернуть ссылку, используя return_value_policy. Однако, используя то, что казалось подходящим (doc); return_value_policy<reference_existing_object>, похоже, не работает.

Я неправильно понял, что он делает?

struct Foo {
    Foo(float x) { _x = x; }
    float& get() { return _x; }
    float  get() const { return _x; }
private:
    float _x;
};

// Wrapper code
BOOST_PYTHON_MODULE(my_module)
{
    using namespace boost::python;
    typedef float (Foo::*get_by_value)() const;
    typedef float& (Foo::*get_by_ref)();

    class_<Foo>("Foo", init<float>())
        .def("get", get_by_value(&Foo::get))
        .def("get_ref", get_by_ref(&Foo::get),
            return_value_policy<reference_existing_object>())//Doesn't work
        ;
}

Примечание. Я знаю, что может быть опасно ссылаться на существующий объект без управления жизненным циклом.

Update:
Похоже, что он работает для объектов, но не для базовых типов данных.
Возьмите этот пересмотренный пример:

struct Foo {
    Foo(float x) { _x = x; }
    float& get() { return _x; }
    float  get() const { return _x; }
    void set( float f ){ _x = f;}
    Foo& self(){return *this;}
private:
    float _x;
};

// Wrapper code
using namespace boost::python;
BOOST_PYTHON_MODULE(my_module)
{
    typedef float (Foo::*get_by_value)() const;

    class_<Foo>("Foo", init<float>())
        .def("get", get_by_value(&Foo::get))
        .def("get_self", &Foo::self,
            return_value_policy<reference_existing_object>())
        .def("set", &Foo::set);
        ;
}

Что в тесте дал ожидаемый результат:

>>> foo1 = Foo(123)
>>> foo1.get()
123.0
>>> foo2 = foo1.get_self()
>>> foo2.set(1)
>>> foo1.get()
1.0
>>> id(foo1) == id(foo2)
False

Ответ 1

В Python существует концепция неизменных типов. Неизменяемый тип не может изменить его значение. Примерами встроенных неизменяемых типов являются int, float и str.

Сказав это, вы не можете делать то, что хотите, с помощью boost::python, потому что сам Python не позволяет вам изменять значение возвращаемого функцией функции float.

В вашем втором примере показано одно решение, другое - создать тонкие обертки и разоблачить это:

void Foo_set_x(Foo& self, float value) {
    self.get() = value;
}

class_<Foo>("Foo", init<float>())
    ...
    .def("set", &Foo_set_x);
;

Это лучшее решение, чем изменение исходного класса С++.

Ответ 3

Я мало знаю о Boost.Python, поэтому я могу неправильно понять вопрос, и в этом случае это совершенно бесполезно. Но здесь идет:

В Python вы не можете выбирать между возвратом по ссылке или по значению, различие не имеет смысла в Python. Мне легче всего думать об этом, поскольку все обрабатывается ссылкой.

У вас просто есть объекты, и у вас есть имена для этих объектов. Так

foo = "ryiuy"

Создает строковый объект "ryiuy", а затем позволяет ссылаться на этот строковый объект с именем "foo". Поэтому в Python, когда вы что-то передали, вы передаете этот объект. В этом нет "значений", поэтому вы не можете передать значение. Но опять же, это также действительная точка зрения, что нет и ссылок, просто объектов и их имен.

Таким образом, я полагаю, что, когда вы получаете ссылку на C, вам нужно передать ссылку на объект, ссылающийся на Python. И когда вы получаете значение в C, вам нужно передать ссылку на объект, который вы создаете из этого значения, на Python.

Ответ 4

Вы уверены, что объект С++ копируется? Каждый раз вы будете получать новый объект python, но который ссылается на один и тот же объект С++. Как вы определяете, что объект был скопирован?