如何在 std::vector 通过 boost::python 的迭代期间使 python 不从 boost::shared_ptr 到 create/copy pyobject?

How to make python not to create/copy pyobject from boost::shared_ptr during iteration on std::vector via boost::python?

我有一个对象和其中的 boost::shared_ptr-s 个对象的 std::vector,我想使用 boost::python 将其转移到 python。 这里的问题是,在对 python 中暴露的向量进行迭代期间,我每次都为同一个对象获得不同的地址。 据我了解,python 在每次迭代时通过我的 boost::shared_ptr 创建 pyobject,并且在迭代之后(实际上是在第二次迭代之后)垃圾收集器释放该地址,尽管元素是 boost::shared_ptr-s。 当元素在 python 的列表中时,它们具有在迭代期间不会改变的唯一地址。这就是我想要实现的目标。 我试图通过在 Whosebug 中获取不同的提升文档和不同的 QA 来上网,但未能解决我的问题。 我创建了一个小例子来演示这个问题。 这是 C++ 代码:

#include <vector>
#include <boost/shared_ptr.hpp>
#include <boost/make_shared.hpp>
#include <boost/python.hpp>

namespace bp = boost::python;

class A {
    public:
        A() : m_int(0) { }
        A(int numb) : m_int(numb) { }
        int get_m() const { return m_int; }
    private:
        int m_int;
};

std::ostream& operator<<(std::ostream& out, const A& obj) {
    out << obj.get_m();
    return out;
}

using APtr = boost::shared_ptr<A>;
using ACont = std::vector<APtr>;
using AContPtr = boost::shared_ptr<ACont>;

APtr CreateA() {
    return boost::make_shared<A>();
}

APtr CreateAFromNumb(int numb) {
    return boost::make_shared<A>(numb);
}

AContPtr CreateEmptyACont() {
    return boost::make_shared<ACont>();
}

AContPtr CreateFullACont(int size) {
    ACont vec{};
    for (int i = 0; i < size; ++i) {
        vec.push_back(boost::make_shared<A>(i));
    }
    return boost::make_shared<ACont>(vec);
}

BOOST_PYTHON_MODULE(example) {
    bp::class_<A, APtr, boost::noncopyable>("A", bp::no_init)
        .def("__init__", bp::make_constructor(CreateA))
        .def("__init__", bp::make_constructor(CreateAFromNumb))
        .def(bp::self_ns::str(bp::self))
        ;
    bp::register_ptr_to_python<APtr>();

    bp::class_<ACont, AContPtr, boost::noncopyable>("ACont", bp::no_init)
        .def("__init__", bp::make_constructor(CreateEmptyACont))
        .def("__init__", bp::make_constructor(CreateFullACont))
        .def("__iter__", bp::iterator<ACont>())
        .def("__len__", &ACont::size)
        ;
    bp::register_ptr_to_python<AContPtr>();
}

下面是它在python中的用法:

#!/usr/bin/env python

from example import *

vec = ACont(4)

print len(vec)
print "VEC", vec
for x in vec:
    print x,
    print repr(x)

print "VEC", vec
for x in vec:
    print x,
    print repr(x)

l = [x for x in vec]

print "LIST"
for x in l:
    print x,
    print repr(x)
print "LIST"
for x in l:
    print x,
    print repr(x)

当遍历公开的 std::vector/boost::shared_ptr 时,我在两次连续的迭代中得到了相同元素的不同地址。

  1. Python 在第一次迭代时为第一项创建 pyobject 并且 显示它的地址。
  2. 在第二次迭代中,python 为另一个地址上的第二个元素创建另一个 pyobject。
  3. 垃圾收集器发现第一个地址不再可用并释放它。
  4. 在第三次迭代中,python 在与第一个元素相同的地址中为第三个元素创建另一个 pyobject,因为 gc 刚刚释放了它。
  5. 以此类推

但是对相同元素列表的迭代是正确完成的,因为 python 没有 create/destruct pyobjects,因为它们在列表本身中有引用。 这是 python 运行 的输出:

4
VEC <example.ACont object at 0x7ffff7ea9578>
0 <example.A object at 0x7ffff7eb27d0>    # address 1
1 <example.A object at 0x7ffff7eb2848>    # address 2
2 <example.A object at 0x7ffff7eb27d0>    # address 1 since gc just have freed it
3 <example.A object at 0x7ffff7eb2848>    # so on
VEC <example.ACont object at 0x7ffff7ea9578>
0 <example.A object at 0x7ffff7eb27d0>
1 <example.A object at 0x7ffff7eb2848>
2 <example.A object at 0x7ffff7eb27d0>
3 <example.A object at 0x7ffff7eb2848>
LIST
0 <example.A object at 0x7ffff7eb27d0>    # address 1
1 <example.A object at 0x7ffff7eb2848>    # address 2
2 <example.A object at 0x7ffff7eb28c0>    # address 3
3 <example.A object at 0x7ffff7eb2938>    # address 4
LIST
0 <example.A object at 0x7ffff7eb27d0>
1 <example.A object at 0x7ffff7eb2848>
2 <example.A object at 0x7ffff7eb28c0>
3 <example.A object at 0x7ffff7eb2938>

总结一下,考虑到这些对象是 boost::shared_ptr-s 的事实,我想知道是否有一种方法不为在 C++ 中创建的每个元素在 python 中创建 pyobjects一些对象。 提前谢谢你。

P.S。我正在使用 boost 版本 1.60 和 python 2.7(anaconda 包)。

所以,经过长时间的调查,我发现我要解决的任务并没有真正的解决方案。这是因为 python 必须注册每个刚出现在其环境中的对象(在别处创建,而不是在 python 中创建)以了解它。因此,关于垃圾收集器的其余场景就像问题中描述的那样。 boost/python 和 swig 都无法处理这种情况。
但是,作为对此的手动解决方案,为了解决寻址问题,我创建了某种地址映射功能。由于我的问题与对象地址有关,我将 C++ 地址转移到 python 以便查看真实对象的地址而不是 python 的地址。 此外,由于我需要处理地址的散列功能,所以我在那里做了同样的事情。这是我的更新

// Same code as previously
std::string GetAddress(const APtr& a_ptr) {
    std::string repr{"<example.A object at "};
    std::stringstream sstream;
    sstream << std::hex << std::showbase << reinterpret_cast<std::uintptr_t>(a_ptr.get());
    repr += sstream.str() + ">";
    return repr;
}

static std::size_t Hash(const APtr& a_ptr) {
    std::hash<std::uintptr_t> hasher;
    return hasher(reinterpret_cast<std::uintptr_t>(a_ptr.get()));
}


BOOST_PYTHON_MODULE(example) {
    bp::class_<A, APtr, boost::noncopyable>("A", bp::no_init)
        .def("__init__", bp::make_constructor(CreateA))
        .def("__init__", bp::make_constructor(CreateAFromNumb))
        .def("__repr__", GetAddress)
        .def("__hash__", Hash)
        .def(bp::self_ns::str(bp::self))
        ;
    bp::register_ptr_to_python<APtr>();

    bp::class_<ACont, AContPtr, boost::noncopyable>("ACont", bp::no_init)
        .def("__init__", bp::make_constructor(CreateEmptyACont))
        .def("__init__", bp::make_constructor(CreateFullACont))
        .def("__iter__", bp::iterator<ACont>())
        .def("__len__", &ACont::size)
        ;
    bp::register_ptr_to_python<AContPtr>();
}

无论如何,感谢大家努力提供帮助。 :)