What C++ Smart Pointer Implementations are available?
Asked Answered
G

3

127

Comparisons, Pros, Cons, and When to Use?

This is a spin-off from a garbage collection thread where what I thought was a simple answer generated a lot of comments about some specific smart pointer implementations so it seemed worth starting a new post.

Ultimately the question is what are the various implementations of smart pointers in C++ out there and how do they compare? Just simple pros and cons or exceptions and gotchas to something you might otherwise think should work.

I've posted some implementations that I've used or at least glossed over and considered using as an answer below and my understanding of their differences and similarities which may not be 100% accurate so feel free to fact check or correct me as needed.

The goal is to learn about some new objects and libraries or correct my usage and understanding of existing implementations already widely in use and end up with a decent reference for others.

Geriatrics answered 17/2, 2011 at 7:29 Comment(24)
I think this should be re-posted as an answer to this question, and the question made into an actual question. Otherwise, I sense people will close this as "not a real question".Offertory
There are all sorts of other smart pointers, e.g. the ATL smart pointers or OpenSceneGraph's osg::ref_ptr.Bassesalpes
Is there a question here?Aesop
"QObject and derived classes, which in the Qt framework is everything" - not quite true. Qt has a lot of classes that don't derive from QObject. Most of them are implicitly shared and rarely allocated on the heap, though. Examples are QString and QDom* classes.Jebel
I think that you've misunderstood std::auto_ptr. std::auto_ptr_ref is a design detail of std::auto_ptr. std::auto_ptr has nothing to do with garbage collection, it's main purpose is specifically to allow exception safe transfer of ownership, especially in function call and function return situations. std::unique_ptr can only solve the "problems" that you cite with standard containers because C++ has changed to allow a distinction between move and copy and standard containers have changed to take advantage of this.Outroar
@Charles It's highly likely the intended use of std::auto_ptr was not really as a smart pointer which would explain the non-copyable nature and transfer of ownership on assignment. I think perhaps wide misuse and misconception of it's intended use partially led to the proposed changes.Geriatrics
@AJG85: In that case, what is your definition of "smart pointer"?Outroar
@Charles: It's a self-destruct pointer.Reputation
@Charles It would be something along the lines of "an object which wraps and contains a raw pointer for the purpose of managing allocation and deallocation of memory safely" ... I edited with a blurb and a link to an article I read when looking up specifically why std::auto_ptr couldn't be safely used in certain cases.Geriatrics
@AJG85: By that definition not many of your quoted objects are smart pointers as few of them manage allocation. I supposed shared_ptr counts because make_shared is part of its interface but your definition seems to be closer to what I would call a 'pimpl' class.Outroar
You say that you’re not an expert in smart pointers but your summary is pretty exhaustive, and correct (except for the minor quibble about auto_ptr_ref being an implementation detail). Still, I agree that you should post this as an answer and reformulate the question to be an actual question. This can then serve as a future reference.Exothermic
@Charles I suppose your right if you want to be argumentative that definition doesn't work as even reset() methods don't call new for you so I would have to remove "allocation and" ... although I'm sure someone has probably implemented something like that before as well.Geriatrics
@AJG85: My definition of smart pointer is probably a lot looser than yours, then. I would consider auto_ptr to be a smart pointer so when you said "the intended use of std::auto_ptr was not really as a smart pointer" I just wanted to clarify exactly what you meant by a smart pointer. I wasn't trying to be picky or argumentative, just clear.Outroar
Voting to open. The question is clearly stated: "Ultimately the question is what are the various implementations of smart pointers in C++ out there and how do they compare?". SO was designed to allow self-answered questions to the benefit of the community. Discussing the merits, strengths, and weaknesses of different pointers will be useful to people, which is the ultimate goal.Slowmoving
@Josh Originally it was all in the question, I didn't manage to edit to the current format fast enough ;)Geriatrics
@AJ unfortunately, that's one of the major problems I have with SO. Questions are voted closed way too quickly, especially when there is a real chance of recovering them quite easily.Slowmoving
@Josh It was an impressive 5 minute margin but I can't complain. Very close attention to minute details is both a good and bad characteristic of programmers.Geriatrics
@AJ agreed, though an attempt at refactoring should be made before throwing out a piece of code =PSlowmoving
@AJG85: "Comparisons, Pros, Cons, and When to Use?" kind of question leads to all kind of subjective answers (Besides this is a self answered question...) I really think this should be rephrase in something like "Wich are the Smart Pointers implementations in C++?" and each one should be in a separated answer.Clevey
Voted to re-open, now that this is an actual question. This might even be worth tagging as c++-faq. I'd do it myself, but I'm not sure which of the existing 5 tags to replace. @Josh: Even though questions can be closed quickly, as you've noticed, they can also be re-opened. No real problem here. The question was changed to be an actual question, and is now answerable. Thus, it has now been re-opened.Aesop
@Alejandro I've edited the question. I'll leave my answer as it stands because it seems excessive to post a self answer for each implementation.Geriatrics
Accepting since no one else seems to have any other implementations to add or corrections.Geriatrics
@Cody Added c++-faq tag, and removed c++-11, as it is going to become the new c++ soon... hopefully.Tonsil
Related: What is a smart pointer and when should I use one?Tonsil
G
236

C++03

std::auto_ptr - Perhaps one of the originals it suffered from first draft syndrome only providing limited garbage collection facilities. The first downside being that it calls delete upon destruction making them unacceptable for holding array allocated objects (new[]). It takes ownership of the pointer so two auto pointers shouldn't contain the same object. Assignment will transfer ownership and reset the rvalue auto pointer to a null pointer. Which leads to perhaps the worst drawback; they can't be used within STL containers due to the aforementioned inability to be copied. The final blow to any use case is they are slated to be deprecated in the next standard of C++.

std::auto_ptr_ref - This is not a smart pointer it's actually a design detail used in conjunction with std::auto_ptr to allow copying and assignment in certain situations. Specifically it can be used to convert a non-const std::auto_ptr to an lvalue using the Colvin-Gibbons trick also known as a move constructor to transfer ownership.

On the contrary perhaps std::auto_ptr wasn't really intended to be used as a general purpose smart pointer for automatic garbage collection. Most of my limited understanding and assumptions are based on Herb Sutter's Effective Use of auto_ptr and I do use it regularly although not always in the most optimized way.


C++11

std::unique_ptr - This is our friend who will be replacing std::auto_ptr it will be quite similar except with the key improvements to correct the weaknesses of std::auto_ptr like working with arrays, lvalue protection via private copy constructor, being usable with STL containers and algorithms, etc. Since it's performance overhead and memory footprint are limited this is an ideal candidate for replacing, or perhaps more aptly described as owning, raw pointers. As the "unique" implies there is only one owner of the pointer just like the previous std::auto_ptr.

std::shared_ptr - I believe this is based off TR1 and boost::shared_ptr but improved to include aliasing and pointer arithmetic as well. In short it wraps a reference counted smart pointer around a dynamically allocated object. As the "shared" implies the pointer can be owned by more than one shared pointer when the last reference of the last shared pointer goes out of scope then the object will be deleted appropriately. These are also thread safe and can handle incomplete types in most cases. std::make_shared can be used to efficiently construct a std::shared_ptr with one heap allocation using the default allocator.

std::weak_ptr - Likewise based off TR1 and boost::weak_ptr. This is a reference to an object owned by a std::shared_ptr and will therefore not prevent the deletion of the object if the std::shared_ptr reference count drops to zero. In order to get access to the raw pointer you'll first need to access the std::shared_ptr by calling lock which will return an empty std::shared_ptr if the owned pointer has expired and been destroyed already. This is primarily useful to avoid indefinite hanging reference counts when using multiple smart pointers.


Boost

boost::shared_ptr - Probably the easiest to use in the most varying scenarios (STL, PIMPL, RAII, etc) this is a shared referenced counted smart pointer. I've heard a few complaints about performance and overhead in some situations but I must have ignored them because I can't remember what the argument was. Apparently it was popular enough to become a pending standard C++ object and no drawbacks over the norm regarding smart pointers come to mind.

boost::weak_ptr - Much like previous description of std::weak_ptr, based on this implementation, this allows a non-owning reference to a boost::shared_ptr. You not surprisingly call lock() to access the "strong" shared pointer and must check to make sure it's valid as it could have already been destroyed. Just make sure not to store the shared pointer returned and let it go out of scope as soon as you're done with it otherwise you're right back to the cyclic reference problem where your reference counts will hang and objects will not be destroyed.

boost::scoped_ptr - This is a simple smart pointer class with little overhead probably designed for a better performing alternative to boost::shared_ptr when usable. It's comparable to std::auto_ptr especially in the fact that it can't be safely used as an element of a STL container or with multiple pointers to the same object.

boost::intrusive_ptr - I've never used this but from my understanding it's designed to be used when creating your own smart pointer compatible classes. You need to implement the reference counting yourself, you'll also need to implement a few methods if you want your class to be generic, furthermore you'd have to implement your own thread safety. On the plus side this probably gives you the most custom way of picking and choosing exactly how much or how little "smartness" you want. intrusive_ptr is typically more efficient than shared_ptr since it allows you to have a single heap allocation per object. (thanks Arvid)

boost::shared_array - This is a boost::shared_ptr for arrays. Basically new [], operator[], and of course delete [] are baked in. This can be used in STL containers and as far as I know does everything boost:shared_ptr does although you can't use boost::weak_ptr with these. You could however alternatively use a boost::shared_ptr<std::vector<>> for similar functionality and to regain the ability to use boost::weak_ptr for references.

boost::scoped_array - This is a boost::scoped_ptr for arrays. As with boost::shared_array all the necessary array goodness is baked in. This one is non-copyable and so can't be used in STL containers. I've found almost anywhere you find yourself wanting to use this you probably could just use std::vector. I've never determined which is actually faster or has less overhead but this scoped array seems far less involved than a STL vector. When you want to keep allocation on the stack consider boost::array instead.


Qt

QPointer - Introduced in Qt 4.0 this is a "weak" smart pointer which only works with QObject and derived classes, which in the Qt framework is almost everything so that's not really a limitation. However there are limitations namely that it doesn't supply a "strong" pointer and although you can check if the underlying object is valid with isNull() you could find your object being destroyed right after you pass that check especially in multi-threaded environments. Qt people consider this deprecated I believe.

QSharedDataPointer - This is a "strong" smart pointer potentially comparable to boost::intrusive_ptr although it has some built in thread safety but it does require you to include reference counting methods (ref and deref) which you can do by subclassing QSharedData. As with much of Qt the objects are best used through ample inheritance and subclassing everything seems to be the intended design.

QExplicitlySharedDataPointer - Very similar to QSharedDataPointer except it doesn't implicitly call detach(). I'd call this version 2.0 of QSharedDataPointer as that slight increase in control as to exactly when to detach after the reference count drops to zero isn't particularly worth a whole new object.

QSharedPointer - Atomic reference counting, thread safe, sharable pointer, custom deletes (array support), sounds like everything a smart pointer should be. This is what I primarily use as a smart pointer in Qt and I find it comparable with boost:shared_ptr although probably significantly more overhead like many objects in Qt.

QWeakPointer - Do you sense a reoccurring pattern? Just as std::weak_ptr and boost::weak_ptr this is used in conjunction with QSharedPointer when you need references between two smart pointers that would otherwise cause your objects to never be deleted.

QScopedPointer - This name should also look familiar and actually was in fact based on boost::scoped_ptr unlike the Qt versions of shared and weak pointers. It functions to provide a single owner smart pointer without the overhead of QSharedPointer which makes it more suitable for compatibility, exception safe code, and all the things you might use std::auto_ptr or boost::scoped_ptr for.

Geriatrics answered 17/2, 2011 at 8:44 Comment(13)
two things I think are worth mentioning: intrusive_ptr is typically more efficient than shared_ptr, since it allows you to have a single heap allocation per object. shared_ptr will in the general case allocate a separate small heap object for the reference counters. std::make_shared can be used to get the best of both worlds. shared_ptr with just a single heap allocation.Ingeborg
I have a perhaps unrelated question: Can garbage collection be implemented by just replacing all pointers by shared_ptrs? (Not counting resolving cyclic references)Lions
@Seth Carnegie: Not all pointers are going to be pointing to something allocated on the free store.Baruch
@In suppose they were (they are in language implementations like Lua I believe).Lions
@Seth Carnegie: Garbage collection can be implemented in a sense by wrapping allocation in something like shared_ptr but you wouldn't want to wrap all pointers. You would also need to make sure raw pointers referencing the same memory increment the ref count by wrapping them in shared_ptr as well as removing any code manually deleting memory owned by the shared_ptr. Basically anytime you use new you could do so with a smart pointer instead.Geriatrics
A further downside of std::auto_ptr<> is that it can't be used in headers with forward-declared types, meaning you can't use it for the Pimpl idiom. By contrast shared_ptr<> (among others) can be used in this way. One disadvantage of shared_ptr though is that it's all-or-nothing: once you pass a raw pointer into it then you can never relinquish ownership of it again.Wes
@the_mandrill: You might be able to get ownership back by swapping in a nullptr although I've never tried as usually the purpose of using a smart pointer is because I want it to own the raw pointer.Geriatrics
Mostly you do want to let shared_ptr own it, but I find sometimes you have cases where you need to relinquish the pointer in order to use it elsewhere, such as with factory methods for instance.Wes
@Wes Why shouldn't you be able to use std::auto_ptr with forward declared types. I've done that and never had any problems with memory leaks. Actually Pimpl is the perfect use-case for std::auto_ptr.Promote
It won't work in the general case as every file that causes the owning class to be destroyed will invoke the Pimpl destructor, which requires a the definition of the Pimpl class. You'll get an incomplete destructor compiler error. shared_ptr uses a trick with a virtual function to get around this problem.Wes
@Wes But it works if the destructor of the owning class is defined in a separate translation unit (.cpp-file) than the client code, which in the Pimpl-idiom is given anyway. Because this translation unit usually knows the complete definition of the Pimpl and therefore its destructor (when it destroys the auto_ptr) correctly destroys the Pimpl. I also had fears for this when I saw those warnings, but I tryed it and it works (the Pimpl's destructor gets called). PS.: please use the @-syntax for me to see any replies.Promote
Usefullness of the list was increased by adding appropriate links to docs.Sining
boost::ptr_vector seems to be missing from the list.Edomite
A
5

There is also Loki which implements policy-based smart pointers.

Other references on policy-based smart pointers, addressing the problem of the poor support of the empty base optimization along with multiple inheritance by many compilers:

Apish answered 2/9, 2011 at 23:42 Comment(0)
I
1

In addition to the ones given, there are some safety oriented ones too:

SaferCPlusPlus

mse::TRefCountingPointer is a reference counting smart pointer like std::shared_ptr. The difference being that mse::TRefCountingPointer is safer, smaller and faster, but does not have any thread safety mechanism. And it comes in "not null" and "fixed" (non-retargetable) versions that can be safely assumed to always be pointing to a validly allocated object. So basically, if your target object is shared among asynchronous threads then use std::shared_ptr, otherwise mse::TRefCountingPointer is more optimal.

mse::TScopeOwnerPointer is similar to boost::scoped_ptr, but works in conjunction with mse::TScopeFixedPointer in a "strong-weak" pointer relationship kind of like std::shared_ptr and std::weak_ptr.

mse::TScopeFixedPointer points to objects that are allocated on the stack, or whose "owning" pointer is allocated on the stack. It is (intentionally) limited in it's functionality to enhance compile-time safety with no runtime cost. The point of "scope" pointers is essentially to identify a set of circumstances that are simple and deterministic enough that no (runtime) safety mechanisms are necessary.

mse::TRegisteredPointer behaves like a raw pointer, except that its value is automatically set to null_ptr when the target object is destroyed. It can be used as a general replacement for raw pointers in most situations. Like a raw pointer, it does not have any intrinsic thread safety. But in exchange it has no problem targeting objects allocated on the stack (and obtaining the corresponding performance benefit). When run-time checks are enabled, this pointer is safe from accessing invalid memory. Because mse::TRegisteredPointer has the same behavior as a raw pointer when pointing to valid objects, it can be "disabled" (automatically replaced with the corresponding raw pointer) with a compile-time directive, allowing it to be used to help catch bugs in debug/test/beta modes while incurring no overhead cost in release mode.

Here is an article describing why and how to use them. (Note, shameless plug.)

Incunabulum answered 13/2, 2016 at 17:54 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.