[C++-sig] shared_ptr trouble
David Abrahams
dave at boost-consulting.com
Mon Mar 13 10:25:01 CET 2006
"Ralf W. Grosse-Kunstleve" <rwgk at yahoo.com> writes:
Cc:'ing Peter Dimov in case he has any brilliant ideas. Peter, I'll
forward you Ralf's complete posting under separate cover.
> I'm having terrible trouble understanding how Boost.Python interacts with
> shared_ptr. I'm trying to implement a small tree node class with parent-child
> relationships via weak_ptr:
>
> class node : boost::noncopyable
> {
> private:
> node() {}
>
> boost::weak_ptr<node> parent_;
>
> public:
> std::string id;
>
> // ...
> };
>
> To be used eventually like this:
>
> node1 = node.create(id="a")
> node2 = node.create(id="b", parent=node1)
>
> node1 has no parent, node2 should use node1 as the parent. The equivalent C++
> code is:
>
> std::string
> exercise_child_parent_id()
> {
> boost::shared_ptr<node> node1 = node::create("a");
> boost::shared_ptr<node> node2 = node::create("b", node1);
> return node2->get_parent_id();
> }
>
> A minimal, self-contained example is attached. The test is simply:
>
> from boost_adaptbx_tree_ext import node, exercise_child_parent_id
Here's the problem:
> def exercise():
> print "C++:", exercise_child_parent_id()
> node1 = node.create(id="a")
> node2 = node.create(id="b", parent=node1)
right here, node1 gets converted to a shared_ptr<node>. Even though
nodes are held by shared_ptr, Boost.Python does not extract the
held_type instance from its Python wrapper. Instead, it creates a
new shared_ptr (with a whole new "control block" containing a
use_count, weak_count and deleter) whose deleter manages a reference
count on the owning Python object. That's the magic that allows
Boost.Python to recover the original Python object when such a
shared_ptr is converted back to Python. Unfortunately, it's also
what causes your problem. The weak_ptr stored in your node is
related to the control block of the new shared_ptr and _not_ to the
one stored inside your Python object. When the Python create( ... )
method returns, that shared_ptr goes away. Since it was the last
non-weak reference to that control block, the weak_ptr thinks the
object is gone...
> print "Python:", node2.get_parent_id()
>
> if (__name__ == "__main__"):
> exercise()
>
> Output:
>
> C++: a
> Python: no parent
>
> The C++ code produces the desired result, but the Python version fails. By
> adding print statements to this function of the node class
>
> static
> boost::shared_ptr<node>
> create(
> std::string const& id,
> boost::shared_ptr<node> const& parent)
You can force Boost.Python to grab the shared_ptr stored in the Python
object by removing "const" from the line above. That signals that a
shared_ptr lvalue is needed and no shared_ptr conjured up on the fly
by the conversion machinery will suffice (this is essentially the rule
temporaries don't bind to non-const references, as implemented by
Boost.Python). On the other hand, when you convert that weak_ptr back
into a shared_ptr, and convert _that_ back to Python, you won't see
the original Python object, but an identical one whose held shared_ptr
refers to the same C++ object. This is a knotty problem and I'm not
sure there's a good answer.
> {
> boost::shared_ptr<node> result(new node);
> result->id = id;
> result->parent_ = parent;
> return result;
> }
>
> I found out that result->parent_->id is in fact "a" right before the return,
> but back in Python the parent is lost. Since node is noncopyable I cannot
> figure out how this can be, and what I need to do to fix the problem. I'd be
> grateful for advice!
--
Dave Abrahams
Boost Consulting
www.boost-consulting.com
More information about the Cplusplus-sig
mailing list