Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it safe to use a weak_ptr in a std::set or key of std::map

There have been a number of questions today regarding std::weak_ptr and std::owner_less and their use in the associative containers std::set and std::map. There are a number of posts stating that using a weak_ptr in a std::set is incorrect, since if the weak pointer expires, it will be Undefined Behavior. Is this correct?

like image 828
Dave S Avatar asked Apr 22 '14 03:04

Dave S


People also ask

Is std :: Weak_ptr thread safe?

Note that the control block used by std::weak_ptr and std::shared_ptr is thread-safe: different non-atomic std::weak_ptr objects can be accessed using mutable operations, such as operator= or reset , simultaneously by multiple threads, even when these instances are copies or otherwise share the same control block ...

What is std :: Weak_ptr?

std::weak_ptr is a smart pointer that holds a non-owning ("weak") reference to an object that is managed by std::shared_ptr. It must be converted to std::shared_ptr in order to access the referenced object.

Why do we need Weak_ptr?

By using a weak_ptr , you can create a shared_ptr that joins to an existing set of related instances, but only if the underlying memory resource is still valid. A weak_ptr itself does not participate in the reference counting, and therefore, it cannot prevent the reference count from going to zero.

What is std :: Owner_less?

struct owner_less<void>; (4) (since C++17) This function object provides owner-based (as opposed to value-based) mixed-type ordering of both std::weak_ptr and std::shared_ptr.


1 Answers

One of the reasons std::owner_less exists is to provide this ordering, and guarantee its safety in the presence of expiring weak pointers. My logic is

First, the definition of std::owner_less

  • operator() defines a strict weak ordering as defined in 25.4

    under the equivalence relation defined by operator(), !operator()(a, b) && !operator()(b, a), two shared_ptr or weak_ptr instances are equivalent if and only if they share ownership or are both empty.

The two cases are

  1. They share the same object, which in practical terms means they share the same reference count object.
  2. They are both empty.

Now, I believe the confusion is over the second term. The key is that "empty" in the standard means that the weak_ptr does not share ownership with any object. Again, the standard states

  • constexpr weak_ptr() noexcept;

    Effects: Constructs an empty weak_ptr object.
    Postconditions: use_count() == 0.

  • weak_ptr(const weak_ptr& r) noexcept;
  • template<class Y> weak_ptr(const weak_ptr<Y>& r) noexcept;
  • template<class Y> weak_ptr(const shared_ptr<Y>& r) noexcept;

    Requires: The second and third constructors shall not participate in the overload resolution unless Y* is implicitly convertible to T*.

    Effects: If r is empty, constructs an empty weak_ptr object; otherwise, constructs a weak_ptr object that shares ownership with r and stores a copy of the pointer stored in r.

    Postconditions: use_count() == r.use_count().

Swap is defined as swapping the states of the two weak_ptrs, and assignment is defined as using the constructors above along with a swap.

They key to note here is that the only way to create an empty weak_ptr is to default construct it, or copy/move/assign one from a previously empty weak_ptr or shared_ptr. It's also important to note that you cannot get an empty weak_ptr by simply letting the weak_ptr expire. An expired weak_ptr simply has a use_count of zero.

As a practical matter, when a shared_ptr is created, a reference count object has to be created, either separate from the data using the shared_ptr constructor, or in the same memory allocation when std::make_shared is used. When a weak_ptr is constructed from that shared_ptr, it will point to that same control structure and reference count. When the shared_ptr is destroyed, it may destroy the data, but the reference count object has to remain until all of the weak_ptr that share ownership are removed. Otherwise, the weak_ptr would have a dangling pointer reference.

So, all of this taken together means that it is safe to use std::weak_ptr as they key of a std::map or in a std::set, as long as you use std::owner_less to perform the ordering. The above guarantees that the ordering of the weak_ptr will remain the same even if it expires while it's in the container.

like image 159
Dave S Avatar answered Sep 28 '22 20:09

Dave S