1

I have a block of code where std::atomic<std::weak_ptr<T>> doesn't behave the way I would have expected if the underlying weak pointer is expired:

std::atomic<std::weak_ptr<Widget>> ptrAtomicWidget = ...;
std::shared_ptr<Widget> ptrWidget = ptrAtomicWidget.load().lock();
while (ptrWidget == nullptr)
{
 ptrWidget = std::make_shared<Widget>();
 std::weak_ptr<Widget> ptrExpected; // <--- nullptr
 std::weak_ptr<Widget> ptrDesired = ptrWidget;
 // Problem Version: Causes an infinite loop when ptrExpected is expired
 if (!ptrAtomicWidget.compare_exchange_weak(ptrExpected, ptrDesired))
 {
 ptrWidget = ptrExpected().lock();
 }
 // Potential Repair Version: *seems* to work (could alternately move declaration of ptrExpected above while loop)
 if (!ptrAtomicWidget.compare_exchange_weak(ptrExpected, ptrDesired)
 && ptrExpected.expired()
 && !ptrAtomicWidget.compare_exchange_weak(ptrExpected, ptrDesired))
 {
 ptrWidget = ptrExpected().lock();
 }
}

The problem I'm having involves the "seems to work" part of the "potential repair version" of the loop body. The repair requires two different expired weak_ptrs to reliably compare equal to each other during the compare_exchange. std::weak_ptr doesn't have an equality operator so consequently its documentation is mute on this. None of the documentation I can find for the specialization of std::atomic<>, for example at CPPReference, describes the behavior of compare-exchange when the pointer is expired. I don't know whether it just happens to work with my particular compiler or whether the C++ standard guarantees it. Does someone know if it is guaranteed to work by the standard?

asked May 14, 2024 at 1:23
4
  • Background: stackoverflow.com/questions/12030650/when-is-stdweak-ptr-useful Commented May 14, 2024 at 1:36
  • "None of the documentation I can find for the specialization of std::atomic<>, for example at CPPReference, describes the behavior of compare-exchange when the pointer is expired." -- The missing piece may be When is a std::weak_ptr empty? Commented May 14, 2024 at 2:28
  • @ShadowRanger "compare_exchange_weak uses a byte-wise comparison," -- You're looking at the general template. There is a specialization for weak_ptr with a different criterion for how values are compared. Commented May 14, 2024 at 2:34
  • @JaMiT: TIL, thanks for that. I'll delete the original comment, but please leave yours so my mistake isn't repeated! Commented May 14, 2024 at 11:52

1 Answer 1

1

You have misunderstood the condition for when compare_exchange would succeed for weak pointers. According to the specification for atomic<weak_ptr<T>>::compare_exchange_weak:

Effects: If p is equivalent to expected, assigns desired to p and has synchronization semantics corresponding to the value of success, otherwise assigns p to expected and has synchronization semantics corresponding to the value of failure.

It is important when two pointers are equivalent, which is also explained:

Remarks: Two weak_ptr objects are equivalent if they store the same pointer value and either share ownership or are both empty. The weak form may fail spuriously. See [atomics.types.operations].

A ptrExpected initialized to nullptr (or nothing) does not store the same pointer value as ptrAtomicWidget (unless it is null), so the first attempt at a compare-exchange is always going to fail.

You can also see why this happens in the libstdc++ implementation (bits/shared_ptr_atomic.h). When _M_ptr == __expected._M_ptr fails (e.g. when one weak_ptr is null and the other isn't), no compare-exchange is attempted and the result is always false.

The second "workaround loop" "works" because when the first compare-exchange inevitably fails, the current value of ptrAtomicWidget is loaded into ptrExpected, which makes it at possible for ptrDesired to replace it upon the second attempt. To me it seems like && ptrExpected.expired() can be removed because you only would have entered the loop if ptrWidget == nullptr, which implies that ptrAtomicWidget is null or expired.


See also When is a std::weak_ptr empty? Is an expired std::weak_ptr empty?

answered May 14, 2024 at 6:03
Sign up to request clarification or add additional context in comments.

1 Comment

Thank you for the helpful references. I think what was throwing me off was that I expected the weak_ptr to be automatically reset to nullptr when no more shared_ptrs own the pointed-to object. Regarding "...so the first attempt at a compare-exchange is always going to fail," in the real code, the weak_ptr is part of the looked-up value in a hash table, so it's null when a value is looked up for the first time.

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.