49

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?

ThiefMaster
  • 310,957
  • 84
  • 592
  • 636
Frank
  • 64,140
  • 93
  • 237
  • 324

13 Answers13

43

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

Kaz Dragon
  • 6,681
  • 2
  • 36
  • 45
  • 1
    good one - in non trivial cases it can be difficult to see the whole dependency chain. Of course that could be a design weakness, but it's a pitfall nonetheless – philsquared Apr 04 '09 at 11:09
  • 2
    @Phil, yeah, that's my pet peeve of design weaknesses: not considering ownership of data when creating objects on the heap. It becomes worse in a multi-threaded project. –  Apr 16 '09 at 12:28
  • @curious: Could you elaborate? weak_ptr basically exists to break such cycles. – Dennis Zickefoose Oct 09 '11 at 02:54
  • 1
    @DennisZickefoose How do you do that exactly? There is a cycle, so what allows you to **pick a strong reference and it make weak**? – curiousguy Oct 09 '11 at 11:40
  • 2
    @curiousguy I'm not sure exactly what the problem you're having is. Just replace one shared_ptr<> with a weak_ptr<> and this breaks the cyclic dependency. If you could elaborate more on the problem you're having, I'll be happy to improve my answer for you. – Kaz Dragon Oct 10 '11 at 09:44
  • 1
    @KazDragon The problem I have is: "_Just replace one shared_ptr<> with a weak_ptr<> and this breaks the cyclic dependency._" is absolute pure nonsense. You could as well say: "replace division with multiplication and you have no more division-by-zero problems" – curiousguy Oct 25 '11 at 07:05
  • 2
    @curious: Interestingly, I have replaced division with multiplication for just that reason. "Absolute nonsense" and "over simplification" are two radically different things. – Dennis Zickefoose Oct 25 '11 at 07:49
  • @curiousguy I have added an example of using weak_ptr<> to break the cycle in the main question. I hope this is sufficient to allay your doubts on this solution. – Kaz Dragon Oct 26 '11 at 09:36
  • 2
    @curiousguy I read another of your answers elsewhere by accident, and I think I understand your point. CMIIW: You argue there is no cyclic dependency, evidenced by the fact that you can put the weak_ptr there in the first place. I find this unhelpful -- it then follows that there is no such a thing as a cyclic dependency: objects are all constructed independently. Therefore, let's define "cyclic dependency" in a useful way, which is "having objects in a state where they have co-dependent lifetimes". I think this is reasonable. Thus, my answer follows from the original question. – Kaz Dragon Oct 27 '11 at 09:15
26

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;
}
Michael Burr
  • 333,147
  • 50
  • 533
  • 760
  • 7
    Of course, the solution here is to never use `new` outside a `shared_ptr` constructor. – rlbond Oct 03 '09 at 02:25
  • 21
    See boost::make_shared() to avoid new completely. :) – Macke Nov 11 '09 at 20:44
  • @MichaelBurr Soo ...in this case we could use pFoo_t pFoo(new foo); is this type of implementation( the example) a case of bad design?– – Aditya P Sep 16 '14 at 18:58
18

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.

Brian Campbell
  • 322,767
  • 57
  • 360
  • 340
  • I agree with the principle but do you know such a compiler that executes things in this weird order?!? I tested with g++ and the order is a "good" one: **1)** _new Foo()_ **2)** _shared_ptr_ **3)** _g()_ – skurton Dec 07 '12 at 13:55
  • @skurton I don't know of a particular compiler that will trigger this, but the order of execution of the `shared_ptr` constructor and `g()` is not specified by the standard, so compilers are free to order it however they like. There are a lot of compilers out there, and they each have many optimization options, and optimizations may depend on many factors in your code; it's never a good idea to depend upon undefined behavior. I'd recommend reading this series on undefined behavior from the Clang developers: [1](http://goo.gl/4VoVj), [2](http://goo.gl/g5j6O), [3](http://goo.gl/QBZmb). – Brian Campbell Dec 07 '12 at 17:47
  • What I meant is that orders A and B are OK: A: new Foo() / shared_ptr() / g() / f(). B: g() / new Foo() / shared_ptr() / f(). A bad order would be: new Foo() / g() / shared_ptr() / f() and I don't think this bad order is standard. – skurton Apr 04 '13 at 15:17
  • 1
    @skurton The standard does not specify that the third order is invalid; thus, compilers are free to choose that order. Here's the [Boost documentation](http://www.boost.org/doc/libs/1_53_0/libs/smart_ptr/shared_ptr.htm) which describes this problem, here's [another explanation](http://www.gotw.ca/gotw/056.htm). See Herb Sutter's book More Exceptional C++ for a more details. Note that in C++11 there's now a [`std::make_shared()`](http://msdn.microsoft.com/en-us/library/vstudio/hh279669.aspx) that allocates the object and wraps it in a shared pointer in a single call, to avoid this problem. – Brian Campbell Apr 04 '13 at 19:29
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).

  • 4
    > "Also, any classes holding shared_ptrs should define copy constructors and assignment operators." ---- Why? Isn't the point of using smart pointers to reduce the need for copy ctor and assignment operator? – Andreas Jan 07 '10 at 12:02
12

Here are two things to avoid:

  • Calling the get() function to get the raw pointer and use it after the pointed-to object goes out of scope.

  • Passing a reference of or a raw pointer to a shared_ptr should be dangerous too, since it won't increment the internal count which helps keep the object alive.

Frank
  • 64,140
  • 93
  • 237
  • 324
  • 1
    However, realize that passing a raw pointer (via get()) is common for calling legacy routines - but you have to know that what you're calling doesn't take ownership (or keep the ptr/ref beyond the object's lifetime). So, it's not ideal but it is something you will lkely have to do. – Michael Burr Mar 31 '09 at 15:35
  • Hopefully not for too long anymore, since shared_ptr is in tr1, as far as I know. (Although I couldn't find it in my g++ 4.3.3 implementation, except for an "internal header file" that is not supopsed to be included in user code.) – Frank Mar 31 '09 at 15:52
10

We debug several weeks strange behavior.

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

Mykola Golubyev
  • 57,943
  • 15
  • 89
  • 102
4

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.

Meredith L. Patterson
  • 4,853
  • 29
  • 30
3

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);
}
Sumant
  • 4,286
  • 1
  • 23
  • 31
1

Giving out a shared_ptr< T > to this inside a class definition is also dangerous. Use enabled_shared_from_this instead.

See the following post here

Community
  • 1
  • 1
George Godik
  • 1,716
  • 1
  • 14
  • 19
1

You need to be careful when you use shared_ptr in multithread code. It's then relatively easy to become into a case when couple of shared_ptrs, pointing to the same memory, is used by different threads.

oo_olo_oo
  • 2,815
  • 5
  • 28
  • 25
0

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

John Morrison
  • 487
  • 4
  • 3
  • 1
    How will opObject=NULL delete an object? Assuming opObject is a pointer? It only sets pointer to null but your object still lives in memory, which is ok if opObject did not own the object, otherwise memory leak. If ptr owns object you need at least delete ptr; ptr = 0 (in C++ we prefer 0 to NULL ...) – stefanB Oct 28 '09 at 00:57
  • I'm going to vote you back up, -2 is harsh and I agree with your main point about shared_ptr having a danger of reference loops that keep objects around permanently. – Zan Lynx Mar 22 '10 at 23:53
-1

If you have a registry of the shared objects (a list of all active instances, for example), the objects will never be freed. Solution: as in the case of circular dependency structures (see Kaz Dragon's answer), use weak_ptr as appropriate.

Mr Fooz
  • 109,094
  • 6
  • 73
  • 101
-5

Smart pointers are not for everything, and raw pointers cannot be eliminated

Probably the worst danger is that since shared_ptr is a useful tool, people will start to put it every where. Since plain pointers can be misused, the same people will hunt raw pointers and try to replace them with strings, containers or smart pointers even when it makes no sense. Legitimate uses of raw pointers will become suspect. There will be a pointer police.

This is not only probably the worst danger, it may be the only serious danger. All the worst abuses of shared_ptr will be the direct consequence of the idea that smart pointers are superior to raw pointer (whatever that means), and that putting smart pointers everywhere will make C++ programming "safer".

Of course the mere fact that a smart pointer needs to be converted to a raw pointer to be used refutes this claim of the smart pointer cult, but the fact that the raw pointer access is "implicit" in operator*, operator-> (or explicit in get()), but not implicit in an implicit conversion, is enough to give the impression that this is not really a conversion, and that the raw pointer produced by this non-conversion is an harmless temporary.

C++ cannot be made a "safe language", and no useful subset of C++ is "safe"

Of course the pursuit of a safe subset ("safe" in the strict sense of "memory safe", as LISP, Haskell, Java...) of C++ is doomed to be endless and unsatisfying, as the safe subset of C++ is tiny and almost useless, as unsafe primitives are the rule rather than the exception. Strict memory safety in C++ would mean no pointers and only references with automatic storage class. But in a language where the programmer is trusted by definition, some people will insist on using some (in principle) idiot-proof "smart pointer", even where there is no other advantage over raw pointers that one specific way to screw the program state is avoided.

curiousguy
  • 8,038
  • 2
  • 40
  • 58
  • 7
    I didn't vote, however I'd say your answer comes across as something of a rant and doesn't really get your point across very well. I think (I hope!) what you really mean is that shared pointers can be used (inappropriately) in situations where there is _no shared ownership_. Safe memory management in C++ generally comes from minimising the number of times you need to remember to call delete, and I absolutely believe that smart pointers (in particular not just shared_ptr but scoped_ptr/unique_ptr too, and together with std containers etc.) should be used to assist in this. – Adam Bowen Oct 24 '11 at 08:20
  • @AdamBowen I don't know what you mean by "rant". Anyway. You can easily see on SO that some people advocate the replacement of almost every pointer with a smart pointer. You can easily see that people on SO don't get the concept of ownership (in particular, that ownership can never be cyclic), and that people wrongly and repeatedly claim on SO that "_`weak_ptr` is used to break cycles_" (citing even the Boost documentation). – curiousguy Oct 25 '11 at 06:59
  • (...) The issue is that smart pointers are good at expressing ownership (especially with rvalue references replacing the the crazy hacks that `auto_ptr` must do). But smart pointers are not even pointers, and cannot replace pointers. **Raw pointers are not bad.** – curiousguy Oct 25 '11 at 06:59
  • Rant (verb) To speak or declaim extravagantly or violently; talk in a wild or vehement way; rave. – Adam Bowen Oct 25 '11 at 08:02
  • @AdamBowen What is extravagant in my answer? – curiousguy Oct 26 '11 at 16:03
  • Terrible answer. Shared pointers provide something called Dynamic Scoping, in a safe manner. Dynamic Scoping is needed in many situations. For other types of scoping, there are other kinds of smart pointers that will serve. Smart people should use them, to make the program clearer and safer. – chila Nov 11 '11 at 17:37
  • It's bad to use raw pointers where the saftey and clearness of smart-pointers outweight their dissadvantages. In most programs, raw pointers should be used rarely. – chila Nov 11 '11 at 17:44
  • @chila "_Shared pointers provide something called Dynamic Scoping_" Hug? What do smart pointers have to do with **scoping**? – curiousguy Nov 19 '11 at 06:41
  • "_It's bad to use raw pointers where the saftey and clearness of smart-pointers outweight their dissadvantages._" They have "dissadvantages"? which "dissadvantages"? – curiousguy Nov 19 '11 at 06:42
  • Well it's not dynamic scoping, but a safe dynamic lifetime, which is needed in many situations. – chila Nov 20 '11 at 18:45
  • Creating, copying and destroying some types of smart pointers (like shared pointers) are slower than doing this to raw pointers. These are the disadvantages. But in many situations the convenience of using smart pointers greatly outweigh these disadvantages. – chila Nov 20 '11 at 18:52
  • @chila "_a safe dynamic lifetime, which is needed in many situations._" Yes, indeed. Smart pointers are useful. `shared_ptr`, `unique_ptr` are useful tools. – curiousguy Nov 20 '11 at 19:25
  • "_Creating, copying and destroying some types of smart pointers (like shared pointers) are slower than doing this to raw pointers._" If that's the issue you have with smart pointers, use `unique_ptr`: it has zero (or almost zero) overhead. Maybe, if you have specific use cases, and you need high performance, you should design your own optimised smart pointer. – curiousguy Nov 20 '11 at 19:31
  • Yes, that is correct. Well to the downvote, I don't think C++ is a language where the the programmer should be trusted to do everything right. That's what the low and zero costs abstractions are for. These abstractions can make many operations safer, at very little or no cost. You made a point saying that one possible dissadvantage is overusing them, but appart from that, you argue things that are not really question-related, and overall it looks like a rant. Only the first chapter is really useful, and I'd upvote it definitely for the "pointer police thing", which is a really funny concept. – chila Nov 21 '11 at 21:54
  • @chila "_I don't think C++ is a language where the the programmer should be trusted to do everything right._" The issue here is that there is **no way** to not trust the programmer in C++ (even if it is possible to detect many mistakes statically). In particular the **smart pointers trust the programmer** to not construct multiple owning pointers holding the same pointer, and to not construct one with a stack object. I am _not_ saying that it is a good idea to trust the programmer, but that **if it is a bad idea to trust the programmer then you simply cannot use C++.** – curiousguy Nov 22 '11 at 01:53
  • (...) "_These abstractions can make many operations safer_" only if they are used correctly. The problem I was trying to explain is that the **hype** of smart pointers, and the misinformation floating around (notably in SO) regarding smart pointers, makes it very unlikely that "believers" of smart pointers (yes, I mean as in a religion) would use smart pointers only where appropriate. In particular I see the idea that raw pointers should be eliminated, and that idea is absurd: a smart pointer must at some point be converted to a raw pointer to be used. – curiousguy Nov 22 '11 at 01:56
  • @chila See: [When to use `shared_ptr` and when to use raw pointers?](http://stackoverflow.com/questions/7657718/when-to-use-shared-ptr-and-when-to-use-raw-pointers) A good example where `shared_ptr` would _not_ be appropriate; I am pleasantly surprised by the fact that answers are based on common sense, and not some notion that smart people use smart pointers and dumb people use dumb pointers. – curiousguy Nov 22 '11 at 03:35
  • (...) What I am trying to express is that "smart" "pointers" **are** going to be misused if programmers are made to believe that they are inherently "superior" to raw pointers. This is the classical problem: it should be up to someone who argue that using a smart pointer is superior to prove his point, not for someone who wants prefers the inherently simpler solution that involves no smart pointer to prove that raw pointers are better. – curiousguy Nov 22 '11 at 06:07
  • @curiousguy: They *are* inherently superior to raw pointers- they guarantee that the memory is used safely unless explicitly subverted. Raw pointers possess no such property. – Puppy Nov 25 '11 at 20:10
  • @DeadMG Raw pointers do guarantee that "the memory is used safely" **unless they are used incorrectly**, exactly like smart pointers. – curiousguy Nov 25 '11 at 22:22
  • The only "rant" here is in the comments, and what the "extravagant" part are the unjustified downvotes. – curiousguy Dec 15 '11 at 05:25