boost::python 和weak_ptr:东西消失了

发布于 2024-12-17 09:55:44 字数 2855 浏览 2 评论 0 原文

我想将对对象的引用存储为weak_ptr。在纯 C++ 中,以下工作:

#include <iostream>

#include <boost/shared_ptr.hpp>
#include <boost/weak_ptr.hpp>

using namespace std;
using namespace boost;

struct Empty
{
    Empty(){}
};

struct Store
{
    weak_ptr<Empty> value;
    Store(){};

    void setValue(shared_ptr<Empty> v) {
        cout << "storing " << v << endl;
        this->value = weak_ptr<Empty>(v);
        shared_ptr<Empty> v_ok = this->value.lock();
        if (v_ok) {
            cout << "ok, v has been stored" << endl;
        }
    }

    shared_ptr<Empty> getValue() {
        shared_ptr<Empty> p = this->value.lock();
        if (p) {
            cout << "stored value : " << p << endl;
        } else {
            cout << "there's nothing here !" << endl;
        }
        return p;
    }
};

int main()
{
    shared_ptr<Empty> e(new Empty);
    shared_ptr<Store> st(new Store);

    st->setValue(e);
    st->getValue();
    return 0;
}

编译并运行它会给你这个:

%> ./a.out 
storing 0x8c6c008
ok, v has been stored
stored value : 0x8c6c008

现在,如果我用 boost python 封装它:

#include <iostream>

#include <boost/shared_ptr.hpp>
#include <boost/python.hpp>
#include <boost/weak_ptr.hpp>

using namespace std;
using namespace boost;
using namespace boost::python;

struct Empty
{
    Empty(){}
};

struct Store
{
    weak_ptr<Empty> value;
    Store(){};

    void setValue(shared_ptr<Empty> v) {
        cout << "storing " << v << endl;
        this->value = weak_ptr<Empty>(v);
        shared_ptr<Empty> v_ok = this->value.lock();
        if (v_ok) {
            cout << "ok, v has been stored" << endl;
        }
    }

    shared_ptr<Empty> getValue() {
        shared_ptr<Empty> p = this->value.lock();
        if (p) {
            cout << "stored value : " << p << endl;
        } else {
            cout << "there's nothing here !" << endl;
        }
        return p;
    }
};

BOOST_PYTHON_MODULE (test)
{
    class_< Empty, shared_ptr<Empty> >("Empty");

    class_< Store, shared_ptr<Store> >("Store")
    .def("get",&Store::getValue)
    .def("set",&Store::setValue);
}

现在用一个小的 python 脚本来尝试

from test import *

e = Empty()
st = Store()

st.set(e)
st.get()

......结果是......

storing 0x9eb2a18
ok, v has been stored
there's nothing here !

所以显然,同时我仍然采用相同的方法(setValue),检索a没有问题 来自 Store::value 的共享指针。但一旦脱离了这个环境,就什么都没有了!

怎么会这样? python是否传递一个全新的(无用的)shared_ptr作为setValue的参数,然后在调用结束时销毁它?我在这里迷路了。

I would like to store a reference to an object as a weak_ptr. In pure C++, the following works :

#include <iostream>

#include <boost/shared_ptr.hpp>
#include <boost/weak_ptr.hpp>

using namespace std;
using namespace boost;

struct Empty
{
    Empty(){}
};

struct Store
{
    weak_ptr<Empty> value;
    Store(){};

    void setValue(shared_ptr<Empty> v) {
        cout << "storing " << v << endl;
        this->value = weak_ptr<Empty>(v);
        shared_ptr<Empty> v_ok = this->value.lock();
        if (v_ok) {
            cout << "ok, v has been stored" << endl;
        }
    }

    shared_ptr<Empty> getValue() {
        shared_ptr<Empty> p = this->value.lock();
        if (p) {
            cout << "stored value : " << p << endl;
        } else {
            cout << "there's nothing here !" << endl;
        }
        return p;
    }
};

int main()
{
    shared_ptr<Empty> e(new Empty);
    shared_ptr<Store> st(new Store);

    st->setValue(e);
    st->getValue();
    return 0;
}

compiling and running this will give you this :

%> ./a.out 
storing 0x8c6c008
ok, v has been stored
stored value : 0x8c6c008

Now, if I encapsulate that with boost python :

#include <iostream>

#include <boost/shared_ptr.hpp>
#include <boost/python.hpp>
#include <boost/weak_ptr.hpp>

using namespace std;
using namespace boost;
using namespace boost::python;

struct Empty
{
    Empty(){}
};

struct Store
{
    weak_ptr<Empty> value;
    Store(){};

    void setValue(shared_ptr<Empty> v) {
        cout << "storing " << v << endl;
        this->value = weak_ptr<Empty>(v);
        shared_ptr<Empty> v_ok = this->value.lock();
        if (v_ok) {
            cout << "ok, v has been stored" << endl;
        }
    }

    shared_ptr<Empty> getValue() {
        shared_ptr<Empty> p = this->value.lock();
        if (p) {
            cout << "stored value : " << p << endl;
        } else {
            cout << "there's nothing here !" << endl;
        }
        return p;
    }
};

BOOST_PYTHON_MODULE (test)
{
    class_< Empty, shared_ptr<Empty> >("Empty");

    class_< Store, shared_ptr<Store> >("Store")
    .def("get",&Store::getValue)
    .def("set",&Store::setValue);
}

and now a small python script to try it out

from test import *

e = Empty()
st = Store()

st.set(e)
st.get()

... and the result is ...

storing 0x9eb2a18
ok, v has been stored
there's nothing here !

so apparently while I'm still in the same method (setValue), there is no problem retrieving a
shared_ptr from Store::value. But as soon as I get out of this context, there's nothing left !

How can this be ? Is python passing a brand new (and useless) shared_ptr as an argument to setValue, which is then destroyed at the end of the call ? I'm lost here.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

断舍离 2024-12-24 09:55:44

这是非常令人好奇的。我已经排除了 std 与 boost 共享指针的可能性,并进行了一些健全性检查,据我所知,boost python 对共享指针所做的事情会破坏它。

跟踪对象构造函数/析构函数,Empty 和 Store 的生命周期按照您的预期进行管理(不会发生复制)。

非常有趣的事情是,shared_from_this 继续工作,即使 weak_ptr<>.lock() 不工作,而且事实上,从新共享指针(来自shared_from_this)创建的新弱指针确实有效。

因此,这引导我评论中链接的线程 ,似乎 boost python 正在使用删除器和引用计数来破坏弱指针。

检查调试器中的共享指针,这就是我们得到的结果:

当我们调用 setValue 时,参数如下所示:

1: p = (const 'boost::shared_ptr<Empty>' &) @0x7fff5fbfe720: {
  px = 0x100346900, 
  pn = {
    pi_ = 0x100338dd0
  }
}
> p *p.pn.pi_
$5 = (boost::detail::sp_counted_impl_pd<void*,boost::python::converter::shared_ptr_deleter>) {
  <boost::detail::sp_counted_base> = {
    _vptr$sp_counted_base = 0x10061aa30, 
    use_count_ = 2, 
    weak_count_ = 2
  }, 
  members of boost::detail::sp_counted_impl_pd<void*,boost::python::converter::shared_ptr_deleter>: 
  ptr = 0x0, 
  del = {
    owner = {
      m_p = 0x10049db90
    }
  }
}

如果我们使用 创建共享指针参数上的shared_from_this,它看起来像这样:

1: p = (const 'boost::shared_ptr<Empty>' &) @0x7fff5fbfe5e0: {
  px = 0x100346900, 
  pn = {
    pi_ = 0x1003468e0
  }
}
> p *p.pn.pi_
$4 = (boost::detail::sp_counted_impl_pd<Empty*,boost::detail::sp_ms_deleter<Empty> >) {
  <boost::detail::sp_counted_base> = {
    _vptr$sp_counted_base = 0x10061b170, 
    use_count_ = 2, 
    weak_count_ = 2
  }, 
  members of boost::detail::sp_counted_impl_pd<Empty*,boost::detail::sp_ms_deleter<Empty> >: 
  ptr = 0x0, 
  del = {
    initialized_ = true, 
    storage_ = {
      data_ = "\000i4\000\001\000\000\000?h4\000\001\000\000", 
      align_ = {<No data fields>}
    }
  }
}

这里需要注意一件事:共享计数的地址不同:这是一个不同的共享指针实例......所以我们'我们以某种方式创建了两个不同的共享指针 地址。这是一件极其糟糕的事情,因为我们预计这很快就会导致双重释放。

然而,事实并非如此。 (如果有人有任何想法,我很想进一步了解这一点?)

说实话,我不知道为什么不这样做(大概这里发生了一些微妙的事情),但无论如何,所有这些确实指出了一个解决方案:我们可以使用 shared_from_this 根据传递的值创建一个共享指针,我们可以使用它来创建一个实际有效的弱指针。

所以,总结一下,修复方法如下:

#include <iostream>
#include <boost/shared_ptr.hpp>
#include <boost/python.hpp>
#include <boost/weak_ptr.hpp>
#include <boost/enable_shared_from_this.hpp>

namespace bp = boost::python;

struct Empty: boost::enable_shared_from_this<Empty>{ };

struct Store
{
    boost::weak_ptr<Empty> value;

    void setValue(boost::shared_ptr<Empty> const& v) {
        value = boost::weak_ptr<Empty>(v->shared_from_this());
        boost::shared_ptr<Empty> v_ok = value.lock();
        if (v_ok) {
            std::cout << "ok, v has been stored" << std::endl;
        }
    }

    boost::shared_ptr<Empty> getValue() {
        boost::shared_ptr<Empty> p = value.lock();
        if (p) {
            std::cout << "stored value : " << p << std::endl;
        } else {
            std::cout << "there's nothing here !" << std::endl;
        }
        return p;
    }
};

BOOST_PYTHON_MODULE (libmylibinterface)
{
    bp::class_< Empty, boost::shared_ptr<Empty> >("Empty",bp::init<>())
        ;

    bp::class_< Store, boost::shared_ptr<Store> >("Store")
        .def("get",&Store::getValue)
        .def("set",&Store::setValue);

}

This is extremely curious. I've ruled out the std vs. boost shared pointer possibility, and played around with a few sanity checks, and as far as I can tell it's something that boost python is doing to the shared pointer that breaks it.

Tracing object constructors / destructors, the lifetimes of Empty and Store are being managed as you would expect (no copies occur).

An extremely interesting thing is that shared_from_this continues to work, even when weak_ptr<>.lock() doesn't, and, in fact, a new weak pointer created from a new shared pointer (from shared_from_this) does work.

So this lead me to the thread linked in the comments, it seems like there's something boost python is doing with the deleter and reference count that is breaking the weak pointer.

Inspecting the shared pointers in the debugger this is what we get:

When we call setValue, this is what the argument looks like:

1: p = (const 'boost::shared_ptr<Empty>' &) @0x7fff5fbfe720: {
  px = 0x100346900, 
  pn = {
    pi_ = 0x100338dd0
  }
}
> p *p.pn.pi_
$5 = (boost::detail::sp_counted_impl_pd<void*,boost::python::converter::shared_ptr_deleter>) {
  <boost::detail::sp_counted_base> = {
    _vptr$sp_counted_base = 0x10061aa30, 
    use_count_ = 2, 
    weak_count_ = 2
  }, 
  members of boost::detail::sp_counted_impl_pd<void*,boost::python::converter::shared_ptr_deleter>: 
  ptr = 0x0, 
  del = {
    owner = {
      m_p = 0x10049db90
    }
  }
}

If we create a shared pointer using shared_from_this on the argument, it looks like this:

1: p = (const 'boost::shared_ptr<Empty>' &) @0x7fff5fbfe5e0: {
  px = 0x100346900, 
  pn = {
    pi_ = 0x1003468e0
  }
}
> p *p.pn.pi_
$4 = (boost::detail::sp_counted_impl_pd<Empty*,boost::detail::sp_ms_deleter<Empty> >) {
  <boost::detail::sp_counted_base> = {
    _vptr$sp_counted_base = 0x10061b170, 
    use_count_ = 2, 
    weak_count_ = 2
  }, 
  members of boost::detail::sp_counted_impl_pd<Empty*,boost::detail::sp_ms_deleter<Empty> >: 
  ptr = 0x0, 
  del = {
    initialized_ = true, 
    storage_ = {
      data_ = "\000i4\000\001\000\000\000?h4\000\001\000\000", 
      align_ = {<No data fields>}
    }
  }
}

There's one thing to note here: the address of the shared count is different: this is a different shared pointer instance... so we've somehow created two different shared pointers to the same address. This is an extremely bad thing, since we'd expect this to shortly result in a double-free.

However, it doesn't. (I'm quite keen to understand this further, if anyone has any ideas?)

I don't know why it doesn't, to be honest (presumably there is something subtle going on here), but in any case, all this does point to a solution: We can use shared_from_this to create a shared pointer, from the value passed, which we can use to create a weak pointer that actually works.

So, to wrap up, here's the fix:

#include <iostream>
#include <boost/shared_ptr.hpp>
#include <boost/python.hpp>
#include <boost/weak_ptr.hpp>
#include <boost/enable_shared_from_this.hpp>

namespace bp = boost::python;

struct Empty: boost::enable_shared_from_this<Empty>{ };

struct Store
{
    boost::weak_ptr<Empty> value;

    void setValue(boost::shared_ptr<Empty> const& v) {
        value = boost::weak_ptr<Empty>(v->shared_from_this());
        boost::shared_ptr<Empty> v_ok = value.lock();
        if (v_ok) {
            std::cout << "ok, v has been stored" << std::endl;
        }
    }

    boost::shared_ptr<Empty> getValue() {
        boost::shared_ptr<Empty> p = value.lock();
        if (p) {
            std::cout << "stored value : " << p << std::endl;
        } else {
            std::cout << "there's nothing here !" << std::endl;
        }
        return p;
    }
};

BOOST_PYTHON_MODULE (libmylibinterface)
{
    bp::class_< Empty, boost::shared_ptr<Empty> >("Empty",bp::init<>())
        ;

    bp::class_< Store, boost::shared_ptr<Store> >("Store")
        .def("get",&Store::getValue)
        .def("set",&Store::setValue);

}
ヅ她的身影、若隐若现 2024-12-24 09:55:44

另一种解决方法是传入对 shared_ptr 的引用,而不是对 shared_ptr 对象本身。在这种情况下,显然 boost::python-Wrapper 并没有创建这个奇怪的单独的 shared_ptr

顺便提一句。如果你查看shared_ptr.pn.pi_指向的类的类型,你会发现分发给Python的“原始”shared_ptr包含一个指向sp_counted_impl_p的指针。 对象,而从 Python 传回的 shared_ptr 包含一个指向sp_counted_impl_pdshared_ptr_deleter>对象(请参阅smart_ptr/detail/sp_counted_impl.hpp
pd 变体不包含对被指点的引用。我怀疑此 sp_counted_impl_pd 对象以某种方式引用 sp_counted_impl_p 对象。这可以解释为什么当从 Python 传回的 shared_ptr 的引用计数降至零时,析构函数不会被调用。事实上 weak_ptr 在这种情况下不起作用可能只是一个错误......?

Another workaround is to pass in a reference to the shared_ptr, not the shared_ptr object itself. In that case obviously boost::python-Wrapper is not creating this strange separate shared_ptr.

Btw. I you look at the type of the class pointed to by shared_ptr.pn.pi_ you'll see that the "original" shared_ptr handed out to Python contains a pointer to a sp_counted_impl_p<POINTEE_TYPE> object while the shared_ptr passed back in from Python contains a pointer to a sp_counted_impl_pd<void*, shared_ptr_deleter> object (see smart_ptr/detail/sp_counted_impl.hpp)
The pd variant does not contain a reference to the pointee. I suspect that this sp_counted_impl_pd object is somehow referencing the sp_counted_impl_p object. This would explain why the destructor is not called when the reference count of the shared_ptr passed back in from Python drops to zero. The fact that weak_ptr does not work in this case might simply be a bug then..?

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文