WeakPtr comparison and ordering semantics

256 views
Skip to first unread message

Joe Mason

unread,
Mar 13, 2025, 7:17:54 PMMar 13
to cxx
Following pkasting's recent talk on c++20 comparison and equality operators* in Chrome, I took a look at adding operator<=> to a class that had a WeakPtr member, and found out that WeakPtr doesn't define any equality or comparison operators, not even the old operator<. It would be convenient to define operator<=> so that classes with WeakPtr members could use defaulted comparison operators, but the semantics are complicated.**

A WeakPtr can be in 3 states:

1. Null (after using the default constructor, calling reset(), or assigning nullptr). `ref_` is invalid, `ptr_` is null.

get() returns nullptr.
operator*() and operator->() will CHECK.
operator bool() returns false.
MaybeValid() returns false.
WasInvalidated() returns false.

2. Pointing to a valid object. `ref_` is valid, `ptr_` is non-null.

get() returns non-null.
operator*() and operator->() return pointers to the object.
operator bool() returns true.
MaybeValid() returns true.
WasInvalidated() returns false.

3. Pointing to an invalidated object. `ref_` is invalid, `ptr_` is non-null.

get() returns nullptr.
operator*() and operator->() will CHECK.
operator bool() returns false.
MaybeValid() returns true or false, racily.
WasInvalidated() returns true.

It would be trivial to define operator== as `lhs.get() == rhs.get()` and `lhs.get() <=> rhs.get()`, but that means that an invalidated WeakPtr compares equal to an explicit nullptr. And using WeakPtr in a sorted container would be a footgun, because the ordering would change as pointers are invalidated.

It would also be possible to define them as `lhs.ptr_ == rhs.ptr_` and `lhs.ptr_ <=> rhs.ptr_`, ignoring the validity. That way WeakPtr's containing the same pointer would continue to be equal and have the same ordering after the pointers are invalidated. I think this is safe because it only compares the values of the pointers, never dereferences them.

A third option would be to get more complicated, and define invalidated pointers as undefined, which matches the comment on `ptr_`:

// This pointer is only valid when ref_.is_valid() is true.  Otherwise, its
// value is undefined (as opposed to nullptr).

I think the semantic of this would be that operator== always returns false if either argument is an invalidated pointer, and operator<=> returns std::partial_ordering::unordered. But this seems potentially very confusing.

I favour comparing `ptr_` members directly, even though this seems to go against the spirit of the comment. Thoughts?

* Google-internal slides at go/cpp20-chromium-1 - is there a public version of that?

** Also the only reasonable way to implement the operators would need them to be called on the thread the WeakPtr is bound to, but I don't think that's a problem. It's just a logical extension of the existing semantics, that a WeakPtr can only by dereferenced or checked for validity on a specific thread.

Joe

Daniel Cheng

unread,
Mar 13, 2025, 7:31:38 PMMar 13
to Joe Mason, cxx
We've intentionally avoided adding `==` for the general case to WeakPtr at all to avoid having to answer questions like this. :)

It opens up a lot of weird edge cases: for example, what happens when WeakPtr<T> is used in a map, but then a WeakPtr is invalidated? What about when someone tries to insert another key/value pair that has the same ptr_ value (due to address reuse) but the validity bit mismatches?

Daniel

--
You received this message because you are subscribed to the Google Groups "cxx" group.
To unsubscribe from this group and stop receiving emails from it, send an email to cxx+uns...@chromium.org.
To view this discussion visit https://groups.google.com/a/chromium.org/d/msgid/cxx/CAH%3DT95T%3DXtUJAvQ6AS-_ixbaktOT5SZy2eShOyqzaga-zRRB7A%40mail.gmail.com.

Michael Lippautz

unread,
Mar 14, 2025, 3:46:14 AMMar 14
to Daniel Cheng, Joe Mason, cxx
On Fri, Mar 14, 2025 at 12:31 AM Daniel Cheng <dch...@chromium.org> wrote:
We've intentionally avoided adding `==` for the general case to WeakPtr at all to avoid having to answer questions like this. :)

It opens up a lot of weird edge cases: for example, what happens when WeakPtr<T> is used in a map, but then a WeakPtr is invalidated? What about when someone tries to insert another key/value pair that has the same ptr_ value (due to address reuse) but the validity bit mismatches?


+1. Order on weak refs (in general, not just WeakPtr) is a footgun because the references are not stable. You can define the operator for a pair of weak refs (picking any of the semantics you wanted) but you would still not be able to rely on the comparisons to generate an order because they may be invalidated at any time.`

Joe Mason

unread,
Mar 17, 2025, 11:53:01 AMMar 17
to Michael Lippautz, Daniel Cheng, cxx
Makes sense. I'll add a comment to weak_ptr.h about why they're not supported
Reply all
Reply to author
Forward
0 new messages