What are potential dangers when using boost::shared_ptr?

What are some ways you can shoot yourself in the foot when using boost::shared_ptr? In other words, what pitfalls do I have to avoid when I use boost::shared_ptr?


Solution 1:

Cyclic references: a shared_ptr<> to something that has a shared_ptr<> to the original object. You can use weak_ptr<> to break this cycle, of course.


I add the following as an example of what I am talking about in the comments.

class node : public enable_shared_from_this<node> {
public :
    void set_parent(shared_ptr<node> parent) { parent_ = parent; }
    void add_child(shared_ptr<node> child) {
        children_.push_back(child);
        child->set_parent(shared_from_this());
    }

    void frob() {
        do_frob();
        if (parent_) parent_->frob();
    }

private :
    void do_frob();
    shared_ptr<node> parent_;
    vector< shared_ptr<node> > children_;
};

In this example, you have a tree of nodes, each of which holds a pointer to its parent. The frob() member function, for whatever reason, ripples upwards through the tree. (This is not entirely outlandish; some GUI frameworks work this way).

The problem is that, if you lose reference to the topmost node, then the topmost node still holds strong references to its children, and all its children also hold a strong reference to their parents. This means that there are circular references keeping all the instances from cleaning themselves up, while there is no way of actually reaching the tree from the code, this memory leaks.

class node : public enable_shared_from_this<node> {
public :
    void set_parent(shared_ptr<node> parent) { parent_ = parent; }
    void add_child(shared_ptr<node> child) {
        children_.push_back(child);
        child->set_parent(shared_from_this());
    }

    void frob() {
        do_frob();
        shared_ptr<node> parent = parent_.lock(); // Note: parent_.lock()
        if (parent) parent->frob();
    }

private :
    void do_frob();
    weak_ptr<node> parent_; // Note: now a weak_ptr<>
    vector< shared_ptr<node> > children_;
};

Here, the parent node has been replaced by a weak pointer. It no longer has a say in the lifetime of the node to which it refers. Thus, if the topmost node goes out of scope as in the previous example, then while it holds strong references to its children, its children don't hold strong references to their parents. Thus there are no strong references to the object, and it cleans itself up. In turn, this causes the children to lose their one strong reference, which causes them to clean up, and so on. In short, this wont leak. And just by strategically replacing a shared_ptr<> with a weak_ptr<>.

Note: The above applies equally to std::shared_ptr<> and std::weak_ptr<> as it does to boost::shared_ptr<> and boost::weak_ptr<>.

Solution 2:

Creating multiple unrelated shared_ptr's to the same object:

#include <stdio.h>
#include "boost/shared_ptr.hpp"

class foo
{
public:
    foo() { printf( "foo()\n"); }

    ~foo() { printf( "~foo()\n"); }
};

typedef boost::shared_ptr<foo> pFoo_t;

void doSomething( pFoo_t p)
{
    printf( "doing something...\n");
}

void doSomethingElse( pFoo_t p)
{
    printf( "doing something else...\n");
}

int main() {
    foo* pFoo = new foo;

    doSomething( pFoo_t( pFoo));
    doSomethingElse( pFoo_t( pFoo));

    return 0;
}

Solution 3:

Constructing an anonymous temporary shared pointer, for instance inside the arguments to a function call:

f(shared_ptr<Foo>(new Foo()), g());

This is because it is permissible for the new Foo() to be executed, then g() called, and g() to throw an exception, without the shared_ptr ever being set up, so the shared_ptr does not have a chance to clean up the Foo object.

Solution 4:

Be careful making two pointers to the same object.

boost::shared_ptr<Base> b( new Derived() );
{
  boost::shared_ptr<Derived> d( b.get() );
} // d goes out of scope here, deletes pointer

b->doSomething(); // crashes

instead use this

boost::shared_ptr<Base> b( new Derived() );
{
  boost::shared_ptr<Derived> d = 
    boost::dynamic_pointer_cast<Derived,Base>( b );
} // d goes out of scope here, refcount--

b->doSomething(); // no crash

Also, any classes holding shared_ptrs should define copy constructors and assignment operators.

Don't try to use shared_from_this() in the constructor--it won't work. Instead create a static method to create the class and have it return a shared_ptr.

I've passed references to shared_ptrs without trouble. Just make sure it's copied before it's saved (i.e., no references as class members).