abergmeier
abergmeier

Reputation: 14052

Missing equality between shared_ptr and weak_ptr

While I do understand why there is no operator== for shared_ptr and unique_ptr, I wonder why there is none for shared_ptr and weak_ptr. Especially since you can create a weak_ptr via a reference on shared_ptr. I would assume that for 99% of the time you want lhs.get() == rhs.get(). I would now go forward and introduce that into my code unless someone can name me a good reason, why one should not do such a thing.

Upvotes: 13

Views: 4838

Answers (3)

rdb
rdb

Reputation: 1582

As the other answers have pointed out, simply comparing the underlying pointers would be perilous. For one, consider the following scenario: a weak reference A exists to an object, which is subsequently deleted, and therefore the weak reference expires. Then, another object is allocated in the memory freed up by said deletion, which has the same address. Now the underlying pointers are the same, even though the weak pointer originally referred to a different object!

As the other answers have suggested, one way is to compare shared == weak.lock(). Since lock() will return nullptr (and not some bogus pointer) if the weak pointer expired, his works for identifying if they are equal (as long as shared != nullptr). However, there are two problems with this:

  • It stops working when the weak pointer expires, in which case the comparison changes; after the expiration, it will only return true if shared == nullptr. This can be dangerous in cases where the comparison must remain stable, such as when using it as a key in an unordered_map or unordered_set.
  • lock() is a relatively expensive operation.

Fortunately, there is a better way to do this. Both weak_ptr and shared_ptr also store a pointer to what is known as the control block, which is what stores the reference counts and outlives the original object for as long as references remain to it. To check whether they refer to the same object, all we need to do is compare the control block pointers. This can be done with the owner_before method:

template<class T>
bool owner_equals(std::shared_ptr<T> &lhs, std::weak_ptr<T> &rhs) {
    return !lhs.owner_before(rhs) && !rhs.owner_before(lhs);
}

This approach will even work for comparing two std::weak_ptrs with each other, if you wish to know whether they (once) referred to the same object, since the control block will last (at least) as long as all of the weak references.

Do keep in mind that this may not produce the expected result if you are using the aliasing feature of std::shared_ptr, which is a feature that lets you create two std::shared_ptr instances with the same control block that nonetheless store different pointers.

Upvotes: 4

CB Bailey
CB Bailey

Reputation: 791869

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.

Upvotes: 19

Matthieu M.
Matthieu M.

Reputation: 299860

Because it has a cost.

A weak_ptr is like an observer, not a real pointer. To do any work with it you first need to obtain a shared_ptr from it using its lock() method.

This has the effect of acquiring ownership, but it as costly as copying a regular shared_ptr (count increment, etc...) so it is nothing trivial.

As such, by not providing ==, you are forced to step back and actually check whether you really need this or not.

Upvotes: 4

Related Questions