3

According to [util.smartptr.weak.obs]/4, the weak_ptr::owner_before strict weak ordering is such that "two shared_­ptr or weak_­ptr instances are equivalent if and only if they share ownership or are both empty."

However, I see nothing in the standard that defines what it means for a weak_ptr to be empty. Obviously a default-constructed weak_ptr is empty, and a weak_ptr constructed from an empty shared_ptr is empty, but it doesn't seem to be explicitly stated whether an expired weak_ptr is empty.

Brian Bi
  • 111,498
  • 10
  • 176
  • 312
  • 1
    I believe an empty `weak_ptr` is an instance where `use_count()` returns 0, as this seems required for `lock()` and `expired()` to work with default constructed, moved from and expired `weak_ptr`s homogeneously. – François Andrieux Aug 05 '22 at 17:55
  • 1
    ["empty" is defined](http://eel.is/c++draft/util.sharedptr#util.smartptr.shared.general-1) for `shared_ptr`: "A `shared_­ptr` is said to be empty if it does not own a pointer." -- obviously this definition doesn't work for `weak_ptr`, since `weak_ptr` never owns a pointer. – Ben Voigt Aug 05 '22 at 18:00
  • @BenVoigt The linked passage seems to imply the opposite. It implies `weak_ptr` can share ownership of an object. – François Andrieux Aug 05 '22 at 18:01
  • @FrançoisAndrieux: `use_count()` say it returns `0` if the `weak_ptr` is empty, but it doesn't say "if and only if". The "number of `shared_ptr` values sharing ownership with `*this`" can be zero, too. – Ben Voigt Aug 05 '22 at 18:02
  • @FrançoisAndrieux: The `shared_ptr` instances have ownership of the target pointer, and they share with `weak_ptr`, but what they share isn't ownership of the target, only ownership of the metadata and access to the target. – Ben Voigt Aug 05 '22 at 18:03
  • @BenVoigt I understand that this is how the ownership scheme works in practice, but as the cited and linked passage shows, it may not be worded that way in the standard. – François Andrieux Aug 05 '22 at 18:05
  • @BenVoigt `expired()` is also [defined](https://timsong-cpp.github.io/cppwp/util.smartptr.weak.obs#2) as `use_count() == 0`, which is where my initial argument stems from. – François Andrieux Aug 05 '22 at 18:07
  • @FrançoisAndrieux: But that argument's invalid, because it failed to consider "not empty, owning a metadata block which shared with zero surviving `shared_ptr` instances" as a possibility. A case which is not ruled out by any of the specifications. To directly address the logical argument -- "empty" implies `use_count() == 0`, and `expired() == true` is equivalent to `use_count() == 0`, but `expired() == 0` does not imply "empty" because `use_count() == 0` does not imply "empty". To claim so is to commit a [fallacy of the converse](https://en.wikipedia.org/wiki/Affirming_the_consequent). – Ben Voigt Aug 05 '22 at 18:29
  • Abstractly, it doesn't "mean" anything for a `weak_ptr` to be empty; it's just a word for a particular state that a `weak_ptr` object could have. It suffices to define how you get an object into or out of that state, and what effect emptiness has on various operations. But the concrete question of whether an expired `weak_ptr` is empty remains a valid question, and could be made even more concrete by asking whether an expired `weak_ptr` must compare equal to an empty `weak_ptr`. – Nate Eldredge Aug 05 '22 at 18:30
  • @NateEldredge: Not "compare equal". "Compare equivalent under the `owner_before` ordering" – Ben Voigt Aug 05 '22 at 18:31
  • @BenVoigt: Yes, right, that is what I meant to say. – Nate Eldredge Aug 05 '22 at 18:44
  • This does raise some other questions about how `owner_before()` should behave on pointers which are expired. It appears in gcc/clang tests that two `weak_ptr`s which formerly shared ownership of the same object, and later expired, are still equivalent under `owner_before()`. Which would imply that they "share ownership" despite the "owned" object no longer existing. – Nate Eldredge Aug 05 '22 at 19:05

1 Answers1

2

While I don't see it guaranteed by the Standard, usability1 of the owner_before ordering requires that:

  • A weak_ptr which has become expired2 is NOT EMPTY.

This is because only mutation of an object should change its placement in an ordering, and a weak_ptr may expire without ever being mutated.


1 For example, here someone has used weak_ptr as a key in std::set: How to compute hash of std::weak_ptr?. Doing that, and std::map likewise, requires the ordering to survive expiration. Were there to be an owner_hash to allow use with std::unordered_set and std::unordered_map, that hash would also have to survive expiration.


2 Note the subtle difference between "has become expired" and "is expired" -- a weak_ptr constructed empty, or assigned with an empty pointer value, has expired() == true. But it didn't expire (action verb), it was created in an expired state. My answer only applies to weak_ptr values which are at some point not expired, then (passively) become expired due to detachment of shared_ptr values from the target.

Ben Voigt
  • 277,958
  • 43
  • 419
  • 720
  • gcc and clang both behave like this: a pointer which has become expired is not equivalent to an empty pointer under `owner_before`. https://godbolt.org/z/4e76jzvT3 – Nate Eldredge Aug 05 '22 at 18:46
  • I actually think the standard does guarantee this implicitly. We normally assume that operations have *only* those observable effects which the standard says they have, and no more. A `weak_ptr` that shares ownership is clearly not empty, and there is nothing in the standard to say that it becomes empty upon expiring. Therefore we should conclude that its emptiness state does not change when it expires, i.e. it remains non-empty. – Nate Eldredge Aug 05 '22 at 18:53
  • Indeed, the only ways the standard specifies for a `weak_ptr` to become empty is to be default constructed, copy-constructed from an empty pointer, move-constructed out of, `swap()`ed with an empty pointer, or some other operation that is defined to do one of those specific things (e.g. assignment to an empty pointer or `reset()`). We can thus assume that a `weak_ptr` does not become empty by any other means (besides UB of course). – Nate Eldredge Aug 05 '22 at 19:00