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

c++, c++11, weak-ptr

Solution

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

- They share the same object, which in practical terms means they share the same reference count object.

- 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_ptr`s, 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.

Problem

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?

Original source