shared_ptr and weak_ptr differences
A shared_ptr
wraps a reference counting mechanism around a raw pointer. So for each instance of the shared_ptr
the reference count is increased by one. If two share_ptr
objects refer the each other they will never get deleted because they will never end up with a reference count of zero.
weak_ptr
points to a shared_ptr
but does not increase its reference count.This means that the underying object can still be deleted even though there is a weak_ptr
reference to it.
The way that this works is that the weak_ptr
can be use to create a shared_ptr
for whenever one wants to use the underlying object. If however the object has already been deleted then an empty instance of a shared_ptr
is returned. Since the reference count on the underlying object is not increased with a weak_ptr
reference, a circular reference will not result in the underlying object not being deleted.
Equality-compare std::weak_ptr
Completely rewriting this answer because I totally misunderstood. This is a tricky thing to get right!
The usual implementation of std::weak_ptr
and std::shared_ptr
that is consistent with the standard is to have two heap objects: the managed object, and a control block. Each shared pointer that refers to the same object contains a pointer to the object and to the control block, and each weak pointer likewise. The control block keeps a record of the number of shared pointers and the number of weak pointers, and deallocates the managed object when the number of shared pointers reaches 0; the control block itself is deallocated when the number of weak pointers also reaches 0.
This is complicated by the fact that the object pointer in a shared or weak pointer can point to a subobject of the actual managed object, e.g. a base class, a member, or even another heap object that is owned by the managed object.
S0 ----------______ MO <------+
\__ `----> BC |
\_ _______--------> m1 |
___X__ m2 --> H |
S1 -/ \__ __----------------^ |
\___ _____X__ |
____X________\__ |
W0 /----------------`---> CB -------+
s = 2
w = 1
Here we have two shared pointers pointing respectively to a base class of the managed object and to a member, and a weak pointer pointing to a heap object owned by the managed object; the control block records that two shared pointers and one weak pointer exist. The control block also has a pointer to the managed object, which it uses to delete the managed object when it expires.
The owner_before
/ owner_less
semantics are to compare shared and weak pointers by the address of their control block, which is guaranteed not to change unless the pointer itself is modified; even if a weak pointer expires because all shared pointers have been destructed, its control block still exists until all weak pointers have also been destructed.
So your equals
code is absolutely correct and thread safe.
The issue is that it's not consistent with shared_ptr::operator==
because that compares the object pointers, and two shared pointers with the same control block can point to different objects (as above).
For consistency with shared_ptr::operator==
, writing t.lock() == u
will be absolutely fine; note however that if it returns true
then it's still not definite that the weak pointer is a weak pointer of the other shared pointer; it could be an alias pointer and so could still expire in following code.
However, comparing control blocks has less overhead (because it doesn't need to look at the control block) and will give the same results as ==
if you're not using alias pointers.
I think that there's something of a deficiency in the standard here; adding an owner_equals
and owner_hash
would allow using weak_ptr
in unordered containers, and given owner_equals
it actually becomes sensible to compare weak pointers for equality, as you can safely compare the control block pointer then the object pointer, since if two weak pointers have the same control block then you know that either both or neither are expired. Something for the next version of the standard, perhaps.
boost, shared ptr Vs weak ptr? Which to use when?
In general and summary,
Strong pointers guarantee their own validity. Use them, for example, when:
- You own the object being pointed at; you create it and destroy it
- You do not have defined behavior if the object doesn't exist
- You need to enforce that the object exists.
Weak pointers guarantee knowing their own validity. Use them, for example, when:
- You access it, but it's not yours.
- You have defined behavior if the object doesn't exist
Lock() on a weak pointer returns a strong pointer; this is how you access the weak pointer. If the object is no longer valid (it's been deleted, etc), then the strong pointer will be NULL, otherwise, it will point at the object. You will need to check this.
It's set up this way so that you cannot accidentally delete the object while you're using it, because you've made a temporary (local) strong pointer, and thus garunteed the object's existence while that strong pointer remains. When you're done using the object, you generally let the strong pointer fall out of scope (or reassigning it), which then allows the object to be deleted. For multithreading, treat them with same care you treat other things that don't have built-in thread safety, noting that the guarantee I mentioned above will hold when multithreading. AFAIK they don't do anything special past that.
The boost shared pointers also have garbage-collector like features, since when the last strong pointer to an object goes away or points somewhere else, the object gets deleted.
There's also the performance and circular dependencies mentioned in the other answers.
Fundamentally, I would say that the boost shared pointer library allows you to not mess up putting together a program, but it is no substitute for taking the time to properly design your pointers, object ownerships and lifetimes. If you have such a design, you can use the library to enforce it. If you don't have such a design, you're likely to run into different problems than before.
Why does shared_ptr needs to hold reference counting for weak_ptr?
The reference count controls the lifetime of the pointed-to-object. The weak count does not, but does control (or participate in control of) the lifetime of the control block.
If the reference count goes to 0
, the object is destroyed, but not necessarily deallocated. When the weak count goes to 0
(or when the reference count goes to 0
, if there are no weak_ptr
s when that happens), the control block is destroyed and deallocated, and the storage for the object is deallocated if it wasn't already.
The separation between destroying and deallocating the pointed-to-object is an implementation detail you don't need to care about, but it is caused by using make_shared
.
If you do
shared_ptr<int> myPtr(new int{10});
you allocate the storage for the int
, then pass that into the shared_ptr
constructor, which allocates storage for the control block separately. In this case, the storage for the int
can be deallocated as early as possible: as soon as the reference count hits 0
, even if there is still a weak count.
If you do
auto myPtr = make_shared<int>(10);
then make_shared
might perform an optimisation where it allocates the storage for the int
and the control block in one go. This means that the storage for the int
can't be deallocated until the storage for the control block can also be deallocated. The lifetime of the int
ends when the reference count hits 0
, but the storage for it is not deallocated until the weak count hits 0
.
Is that clear now?
Missing equality between shared_ptr and weak_ptr
weak_ptr
doesn' have a get()
method because you need to explicitly lock the weak_ptr
before you can access the underlying pointer. Making this explicit is a deliberate design decision. If the conversion were implicit it would be very easy to write code that would be unsafe if the last shared_ptr
to the object were to be destroyed while the underlying pointer obtained from the weak_ptr
was still being examined.
This boost page has a good description of the pitfalls and why weak_ptr
has such a limited interface.
If you need to do a quick comparison, then you can do shared == weak.lock()
. If the comparison is true then you know that weak
must still be valid as you hold a separate shared_ptr
to the same object. There is no such guarantee if the comparison returns false.
Why shared_ptr's reference counting object needs to keep track of the number of weak_ptrs pointing to the object too?
std::weak_ptr
refers to the control block to know if the object still exists and if so, to provide a std::shared_ptr
to it when needed. For that reason, the control block must exist as long as either a std::weak_ptr
or a std::shared_ptr
exists. You need to track the number of instances of std::weak_ptr
to know when the last one is destroyed, just like for std::shared_ptr
.
When is std::weak_ptr useful?
A good example would be a cache.
For recently accessed objects, you want to keep them in memory, so you hold a strong pointer to them. Periodically, you scan the cache and decide which objects have not been accessed recently. You don't need to keep those in memory, so you get rid of the strong pointer.
But what if that object is in use and some other code holds a strong pointer to it? If the cache gets rid of its only pointer to the object, it can never find it again. So the cache keeps a weak pointer to objects that it needs to find if they happen to stay in memory.
This is exactly what a weak pointer does -- it allows you to locate an object if it's still around, but doesn't keep it around if nothing else needs it.
Related Topics
Std::Vector Capacity After Copying
Ptr->Hello(); /* Versus */ (*Ptr).Hello();
Possible Memory Leak Without a Virtual Destructor
Why the Initializer of Std::Function Has to Be Copyconstructible
Is It Ok Not to Call the Destructor on Placement New Allocated Objects
Are the "Usual Arithmetic Conversions" and the "Integer Promotions" the Same Thing
When Should I Use the Keyword "Typename" When Using Templates
Why Do Two Functions Have the Same Address
C++ - Std::Thread Crashes Upon Execution
Passing Constexpr Objects Around
C++11 Range Based Loop: How Does It Really Work
"Cannot Evaluate Function -- May Be In-Lined" Error in Gdb for Stl Template Container
For Loop Prints an Extra Comma
C++ Multi-Line Comments Using Backslash
Deleted Function in Std::Pair When Using a Unique_Ptr Inside a Map
How to Program for Windows Phone 7 in Standard C++ Only
Qmake: How to Remove Compiler Flag for a Certain Project, Without Changing Qmake.Conf