Shared_Ptr and Weak_Ptr Differences

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



Leave a reply



Submit