Thread: When to use shared_ptr?

  1. #1
    Registered User
    Join Date
    Jun 2008
    Posts
    7

    When to use shared_ptr?

    Hi, I have seen several responses to this question before, ranging from "Never! Smart pointers are evil!" to "Only when you have resources that the main framework does not directly track." to "Always, unless there is need for extreme performance, or using them is a practical impossibility."

    I have two types of object, say Root and Entity. Root creates and "owns" Entities, i.e. Entity will not operate if the Root object has been destroyed. References to both types of object can be passed to the user. If I use smart pointers, the user controls the lifetime of the Entities, but once Root has been destroyed, none of the Entities will function any more. The way I see to handle this is to store a weak_ptr to Root in each Entity. Every time an Entity needs Root to exists to function properly, it can check the existence of Root through its weak_ptr. The problem with this, is if there is another class SubEntity which is owned in a similar way by Entity. Thus, if Root is destroyed, the Entities will still exist, and SubEntity will have to check its validity by somehow checking the entire chain all the way down to Root (this could be done in various ways). This seems very long winded, but it does mean that the framework can deal easily with invalid function calls by the user.

    The problem I have is that in my framework, nearly all the classes exist in hierarchical patterns such as the above. If fact, I can imagine that many frameworks operate like this, and this is why I am unsure about the use of shared_ptrs in general. Is this a situation where it would be best not to use smart pointers, and have the risk of the user dereferencing an invalid pointer? Or is using smart pointer here worth the effort? Or maybe I am missing something completely.

    Thanks for your time, I greatly appreciate it.

    Joe

  2. #2
    Registered User
    Join Date
    Jan 2005
    Posts
    7,366
    >> "Never! Smart pointers are evil!"
    Just curious, who said this?

    >> Is this a situation where it would be best not to use smart pointers, and have the risk of
    >> the user dereferencing an invalid pointer?
    At worst, you run the same risk when using the smart pointer, you don't run any greater risk. Using the shared_ptr gives you an opportunity to manage the hierarchy through weak_ptrs, so there is a benefit there.

    >> if Root is destroyed, the Entities will still exist
    I'm confused about this part, though. If Root is destroyed, and it is the only object holding a shared_ptr that owns the Entities (with other objects holding weak_ptrs), then the Entities will be destroyed right then, correct? That means that the SubEntities will be destroyed as well assuming they follow the same pattern. All weak_ptrs referring to the Entities and SubEntities will still exist, but there would be no need to check the entire chain, right?

  3. #3
    Registered User
    Join Date
    Jun 2008
    Posts
    7
    >> "Never! Smart pointers are evil!"
    Just curious, who said this?
    No one inparticular. I've seen many people write something along the lines of this. I've always assumed they are just stuck in their old ways, or just have never really looked at the benefits.

    >> Is this a situation where it would be best not to use smart pointers, and have the risk of
    >> the user dereferencing an invalid pointer?
    At worst, you run the same risk when using the smart pointer, you don't run any greater risk. Using the shared_ptr gives you an opportunity to manage the hierarchy through weak_ptrs, so there is a benefit there.
    Shared pointers are meant to prevent access of deleted objects, by ensuring they aren't deleted while references to the object still exist. I fail to see how I am still running that risk while using shared pointers (given that I don't stupidly delete the object manually).

    >> if Root is destroyed, the Entities will still exist
    I'm confused about this part, though. If Root is destroyed, and it is the only object holding a shared_ptr that owns the Entities (with other objects holding weak_ptrs), then the Entities will be destroyed right then, correct? That means that the SubEntities will be destroyed as well assuming they follow the same pattern. All weak_ptrs referring to the Entities and SubEntities will still exist, but there would be no need to check the entire chain, right?
    Sure, if the user no longer holds any references to the Entity, it will be deleted. If they do, the Entity will still exist, but will no longer function correctly due to the deleted Root. SubEntity needs Entity and Root to function correctly. Another possibility would be for Root to notify all Entities of its deletion, which would in turn notify all SubEntities, removing the need to check weak_ptrs every time. This is a bit of a contrived example, but it demonstrates my situation well (I think :P).

  4. #4
    Registered User
    Join Date
    Apr 2008
    Posts
    890
    I've found resistance to use STL, templates, etc. from some game programmers who put speed above all else.

  5. #5
    Registered User
    Join Date
    Jan 2005
    Posts
    7,366
    >> I fail to see how I am still running that risk while using shared pointers (given that I don't
    >> stupidly delete the object manually).
    That was my point. The shared_ptr should be at least as safe.

    >> Sure, if the user no longer holds any references to the Entity, it will be deleted.
    If the user holds any shared_ptr references to the Entity it will not be deleted. If they hold weak_ptr references then it will still be deleted, so you don't have the problem. Am I missing something?

  6. #6
    Registered User
    Join Date
    Jun 2008
    Posts
    7
    >> Sure, if the user no longer holds any references to the Entity, it will be deleted.
    If the user holds any shared_ptr references to the Entity it will not be deleted. If they hold weak_ptr references then it will still be deleted, so you don't have the problem. Am I missing something?
    Well, like you said, if the user holds shared_ptr references the Entity will not be deleted, and the user will still be able to use it, therefore some extra checks/coordination between the objects will need to happen to avoid undefined behaviour. I'm not expecting the user to keep only weak_ptrs, as I think it would be very inconvenient to have to lock the pointer on every use.

    I'm not really looking for a working solution to a particular problem, I'm more wondering how/if other people would use smart pointers in this situation.

  7. #7
    Registered User
    Join Date
    Jan 2005
    Posts
    7,366
    But the user should be holding weak_ptrs, not shared_ptrs, if it is not supposed to share ownership.

    >> I'm not expecting the user to keep only weak_ptrs, as I think it would be very inconvenient
    >> to have to lock the pointer on every use.
    Forgive me, as I have limited experience with weak_ptrs, so I don't know what you mean by locking the pointer on every use. In my mind, only objects that share ownership of the resource should get a shared_ptr to that resource. I was under the impression that everybody else should get a weak_ptr.

  8. #8
    Registered User
    Join Date
    Jun 2008
    Posts
    7
    You are correct, the idea of weak_ptrs is so that you can hold a reference to the object without having ownership. To access the object using weak_ptr, you have to lock it (by calling the lock() method, at least in boost anyway), which creates a shared_ptr from the weak_ptr if the reference count hasn't reached zero (and returns a null pointer if not). Since weak_ptrs can be "turned into" shared_ptrs, a user could potentially have ownership of the object even if you only give them a weak_ptr. Thus, it is not only inconvenient for the user, the safety of the system relies on the user not storing a locked pointer.

    My point is, I think weak_ptrs are generally only for use internally.

  9. #9
    Cat without Hat CornedBee's Avatar
    Join Date
    Apr 2003
    Posts
    8,895
    By locking, he means this: a weak_ptr (as in Boost's or TR1's) doesn't give you access to the pointee. It doesn't overload * or -> and doesn't give you any other way of getting at the pointee, except one: you can create a shared_ptr from the weak_ptr. If the weak_ptr is invalid, this throws.

    This behaviour ensures that an object isn't deleted while in direct use by some code holding a weak pointer to it.

    However:
    as I think it would be very inconvenient to have to lock the pointer on every use.
    You have to do the right thing conceptually, even if you feel it's inconvenient. (Not that it is all that inconvenient, really.) If you mess up your conceptual correctness, you immediately lose the ability to reason about the state of your objects, and that's absolutely the last thing you want to happen. In my opinion, every memory leak and invalid access is, fundamentally, a failure to correctly reason about the state of the object, so you want to make this as simple as possible.
    All the buzzt!
    CornedBee

    "There is not now, nor has there ever been, nor will there ever be, any programming language in which it is the least bit difficult to write bad code."
    - Flon's Law

  10. #10
    Registered User
    Join Date
    Jan 2005
    Posts
    7,366
    Ok, so it sounds like a shared_ptr is the way to go here, then. Right? No matter what, you have to account for the possibility that the object might be deleted, either by locking the weak_ptr or by doing extra work with a raw pointer. So using the built-in functionality of the weak_ptr seems like it would make the most sense rather than creating your own setup.

    Locking shouldn't be that hard. Do it once for each time you need to use the pointer in a specific scope. Unless you're using threads (which would be an entirely different issue anyway), you can feel safe that the locking won't stop the object from being destroyed since it cannot be destroyed until you leave your function (of course assuming you don't delete the Root or parent Entity later in that same function).

    Is there really that much code outside of the hierarchy that will refer to these pointers? Anything inside the hierarchy would probably be fine if it also gets cleaned up at the same time. One reason I might not have had this problem, is that in most of my scenarios like that there is nothing outside the hierarchy that keeps a reference to child objects.

  11. #11
    Registered User
    Join Date
    Jun 2008
    Posts
    7
    I see your reasoning. I guess the consensus here is that you should make use of smart pointers wherever possible. I shouldn't be accessing the objects too frequently externally, just for setting the initial object state, and if I do, I could probably make use of callbacks instead. Thanks both.

    I'm still open to any other opinions though.

  12. #12
    int x = *((int *) NULL); Cactus_Hugger's Avatar
    Join Date
    Jul 2003
    Location
    Banks of the River Styx
    Posts
    902
    Quote Originally Posted by medievalelks View Post
    I've found resistance to use STL, templates, etc. from some game programmers who put speed above all else.
    Hmm. I'm in my college's local Gamedev club, and we've used the STL in projects. Never had a problem with speed, except when I pushed it well beyond what I'm intending it to do. I've even looked at a few STL implementations, such as std::list, and I'm not sure if I could do much better.

    Although, while tutoring some CS students, it does seem that MSVC++'s STL is _much_ slower in debug mode, but... it is debug mode. (They were learning about big-O notation, with different sort implementations, and the combination of debug+bubble sort was... >.< The debug was like a x3 slowdown (still maintained correct big-O-ness though, just slower, but the difference between 3minutes and 9minutes is nice) )
    long time; /* know C? */
    Unprecedented performance: Nothing ever ran this slow before.
    Any sufficiently advanced bug is indistinguishable from a feature.
    Real Programmers confuse Halloween and Christmas, because dec 25 == oct 31.
    The best way to accelerate an IBM is at 9.8 m/s/s.
    recursion (re - cur' - zhun) n. 1. (see recursion)

  13. #13
    Cat without Hat CornedBee's Avatar
    Join Date
    Apr 2003
    Posts
    8,895
    Someone filed a bug against GCC because the debug implementation of some algorithm went from O(n log n) to O(n^2).

    Also, some people have reported an order of magnitude speedup by disabling the MS STL's secure mode. This depends on the way you use the STL, of course.
    All the buzzt!
    CornedBee

    "There is not now, nor has there ever been, nor will there ever be, any programming language in which it is the least bit difficult to write bad code."
    - Flon's Law

  14. #14
    Registered User
    Join Date
    Jul 2008
    Posts
    3
    For all but a very few circumstances (kernel development, embedded systems, intricately threaded code), you should *always* use a smart pointer when dealing with pointers. By a smart pointer, I mean a smart pointer in Boost or Loki libraries, auto_ptr has serious limitations.

    For arrays - use vector. Don't bother trying to implement you're own unless you're trying to learn; the STL that comes with your compiler is safer and most likely faster than your own custom array class.

  15. #15
    Registered User
    Join Date
    May 2008
    Location
    Paris
    Posts
    248
    For arrays - use vector
    and what about "valarray" ? I've read some negative comments about it, but it seems to be much faster and more memory efficient. No exception handling, no bound checks, ...

    Lots of undefined behaviour, but it might be advantageous for large matrix inversions for example.

Popular pages Recent additions subscribe to a feed

Tags for this Thread