What are potential dangers when using boost::shared_ptr?

后端 未结 13 750
猫巷女王i
猫巷女王i 2020-12-02 07:42

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?

相关标签:
13条回答
  • 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).

    0 讨论(0)
  • 2020-12-02 08:35

    Not precisely a footgun, but certainly a source of frustration until you wrap your head around how to do it the C++0x way: most of the predicates you know and love from <functional> don't play nicely with shared_ptr. Happily, std::tr1::mem_fn works with objects, pointers and shared_ptrs, replacing std::mem_fun, but if you want to use std::negate, std::not1, std::plus or any of those old friends with shared_ptr, be prepared to get cozy with std::tr1::bind and probably argument placeholders as well. In practice this is actually a lot more generic, since now you basically end up using bind for every function object adaptor, but it does take some getting used to if you're already familiar with the STL's convenience functions.

    This DDJ article touches on the subject, with lots of example code. I also blogged about it a few years ago when I first had to figure out how to do it.

    0 讨论(0)
  • 2020-12-02 08:42

    We debug several weeks strange behavior.

    The reason was:
    we passed 'this' to some thread workers instead of 'shared_from_this'.

    0 讨论(0)
  • 2020-12-02 08:42

    Using shared_ptr for really small objects (like char short) could be an overhead if you have a lot of small objects on heap but they are not really "shared". boost::shared_ptr allocates 16 bytes for every new reference count it creates on g++ 4.4.3 and VS2008 with Boost 1.42. std::tr1::shared_ptr allocates 20 bytes. Now if you have a million distinct shared_ptr<char> that means 20 million bytes of your memory are gone in holding just count=1. Not to mention the indirection costs and memory fragmentation. Try with the following on your favorite platform.

    void * operator new (size_t size) {
      std::cout << "size = " << size << std::endl;
      void *ptr = malloc(size);
      if(!ptr) throw std::bad_alloc();
      return ptr;
    }
    void operator delete (void *p) {
      free(p);
    }
    
    0 讨论(0)
  • 2020-12-02 08:43

    The popular widespread use of shared_ptr will almost inevitably cause unwanted and unseen memory occupation.

    Cyclic references are a well known cause and some of them can be indirect and difficult to spot especially in complex code that is worked on by more than one programmer; a programmer may decide than one object needs a reference to another as a quick fix and doesn't have time to examine all the code to see if he is closing a cycle. This hazard is hugely underestimated.

    Less well understood is the problem of unreleased references. If an object is shared out to many shared_ptrs then it will not be destroyed until every one of them is zeroed or goes out of scope. It is very easy to overlook one of these references and end up with objects lurking unseen in memory that you thought you had finished with.

    Although strictly speaking these are not memory leaks (it will all be released before the program exits) they are just as harmful and harder to detect.

    These problems are the consequences of expedient false declarations: 1. Declaring what you really want to be single ownership as shared_ptr. scoped_ptr would be correct but then any other reference to that object will have to be a raw pointer, which could be left dangling. 2. Declaring what you really want to be a passive observing reference as shared_ptr. weak_ptr would be correct but then you have the hassle of converting it to share_ptr every time you want to use it.

    I suspect that your project is a fine example of the kind of trouble that this practice can get you into.

    If you have a memory intensive application you really need single ownership so that your design can explicitly control object lifetimes.

    With single ownership opObject=NULL; will definitely delete the object and it will do it now.

    With shared ownership spObject=NULL; ........who knows?......

    0 讨论(0)
  • 2020-12-02 08:45

    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<>.

    0 讨论(0)
提交回复
热议问题