pybind11 поддерживая объекты живыми - PullRequest
1 голос
/ 30 января 2020

Я изучал один из тестовых файлов в pybind11 и натолкнулся на различное использование keep_alive.

py::keep_alive<1, 2>
py::keep_alive<1, 0>
py::keep_alive<0, 1>

Может ли кто-нибудь пролить свет на эти случаи использования в этом тестовом файле? Я знаю, что индекс 0 относится к указателю возврата, от 1 до this. Я могу понять только py::keep_alive<1, 2> (используя документацию), но не использовать его в этом тестовом файле.

class Child {
public:
    Child() { py::print("Allocating child."); }
    Child(const Child &) = default;
    Child(Child &&) = default;
    ~Child() { py::print("Releasing child."); }
};
py::class_<Child>(m, "Child")
    .def(py::init<>());

class Parent {
public:
    Parent() { py::print("Allocating parent."); }
    ~Parent() { py::print("Releasing parent."); }
    void addChild(Child *) { }
    Child *returnChild() { return new Child(); }
    Child *returnNullChild() { return nullptr; }
};
py::class_<Parent>(m, "Parent")
    .def(py::init<>())
    .def(py::init([](Child *) { return new Parent(); }), py::keep_alive<1, 2>())
    .def("addChild", &Parent::addChild)
    .def("addChildKeepAlive", &Parent::addChild, py::keep_alive<1, 2>())
    .def("returnChild", &Parent::returnChild)
    .def("returnChildKeepAlive", &Parent::returnChild, py::keep_alive<1, 0>())
    .def("returnNullChildKeepAliveChild", &Parent::returnNullChild, py::keep_alive<1, 0>())
    .def("returnNullChildKeepAliveParent", &Parent::returnNullChild, py::keep_alive<0, 1>());

1 Ответ

2 голосов
/ 01 февраля 2020

В реальном коде функция addChild была бы реализована, если хранить объектный указатель Parent в качестве указателя, не вступая во владение (т. Е. Позже он не удалит его на стороне C ++). Что делает py::keep_alive<1, 2>, так это помещает ссылку из объекта Parent на объект Child, переданный в addChild, таким образом связывая время жизни Child с временем жизни Parent.

Таким образом, если написать:

p = Parent()
p.addChild(Child())

, то без keep_alive этот временный Child объект выйдет из области видимости go (ref-count down to zero) на следующей строке. Вместо этого с keep_alive<1, 2> происходит следующее (псевдокод):

p = Parent()
c = Child()
p.__keep_alive = c
p.addChild(c)
del c

Так что теперь, когда p выходит из области видимости, его данные очищаются, в т.ч. ссылка __keep_alive, в которой c также очищается. Значение p и «временный» дочерний элемент c go выходят за рамки одновременно и не ранее.

РЕДАКТИРОВАТЬ : для keep_alive<0, 1> - время жизни неявный this привязан к возвращаемому значению. В тесте он используется только для проверки того, что эта политика может работать с возвратом None, но обычно при доступе к внутреннему элементу данных временного типа обычно имеет дело с промежуточными временными переменными в длинном выражении, например:

c = getCopyOfData().at(0).getField('f')

Проблема в том, что в C ++ время существования временных значений остается до конца оператора, поэтому вышеприведенное будет распространено в транслитерированном коде. Но в Python он заканчивается подсчетом повторений, равным 0. А вот, результат getCopyOfData() исчезнет после завершения вызова at(0), оставив getField() в удаленной памяти. Вместо этого с keep_alive<0, 1> это будет (псевдокод):

d = getCopyOfData()
at0 = d.at(0)
at0.__keep_alive = d
del d
c = at0.getField('f')
c.__keep_alive = at0
del at0

Так что теперь скопированный контейнер данных d не будет go вне области видимости, пока не будет сделана ссылка на доступное поле выходит за рамки.

Для keep_alive<1, 0> время жизни возвращаемого значения привязано к неявному this. Это полезно, если владение передается вызывающей стороне, а неявный this сохраняет указатель, фактически откладывая управление памятью с C ++ до Python. Помните, что в pybind11 идентичность объекта сохраняется, поэтому любой вызов returnChildKeepAlive, возвращающий тот же указатель, приведет к тому же Python объекту, а не к новому. Таким образом, в этом случае (псевдокод):

c = p.returnChildKeepAlive()    # with c now owning the C++ object
p.__keep_alive = c

Если ссылка c выходит первой из области видимости, p все равно будет поддерживать ее работу, чтобы не застрять с висящим указателем. Если p выходит первым из области видимости, c не будет затронут, потому что он перешел во владение (то есть сторона C ++ не будет удалена). И если returnChildKeepAlive() вызывается во второй раз, он вернет ссылку на ожидающий c, а не новый прокси-сервер, что не повлияет на общее управление временем жизни.

...