C++标准是否保证过期std::weak_ptr的std::atomic compare_exchange操作有效?
我写了一段使用std::atomic<std::weak_ptr<T>>的代码,发现底层weak_ptr过期时行为不符合预期:
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; // 有问题的版本:当ptrExpected过期时会导致无限循环 if (!ptrAtomicWidget.compare_exchange_weak(ptrExpected, ptrDesired)) { ptrWidget = ptrExpected.lock(); } // 潜在修复版本:*似乎*能正常工作(也可以把ptrExpected的声明移到while循环外) if (!ptrAtomicWidget.compare_exchange_weak(ptrExpected, ptrDesired) && ptrExpected.expired() && !ptrAtomicWidget.compare_exchange_weak(ptrExpected, ptrDesired)) { ptrWidget = ptrExpected.lock(); } }
这个潜在修复版本的问题在于:它依赖两个不同的过期weak_ptr在compare_exchange操作中能被可靠判定为相等。但std::weak_ptr本身没有直接的相等运算符规范,相关文档也没提这个场景;同时std::atomic<>特化的文档也没说明指针过期时compare_exchange的行为。我不确定这只是编译器的偶然表现,还是C++标准对此有明确保证?
标准层面的明确保证
C++标准对std::atomic<std::weak_ptr<T>>的compare_exchange_weak及相关原子操作的相等判定逻辑,是基于std::weak_ptr的底层控制块标识,和指针是否过期无关:
- 当两个
std::weak_ptr指向同一个控制块时(无论对应对象是否已销毁、weak_ptr是否过期),原子比较操作会判定二者相等。 - 若两个
std::weak_ptr指向不同控制块(包括其中一个是默认构造的空weak_ptr),则判定为不相等。
针对你的代码场景:
- 默认构造的
ptrExpected是空weak_ptr,对应空控制块标识。 - 当
ptrAtomicWidget存储过期weak_ptr时,它指向的是一个已无托管对象的控制块——这个控制块和空weak_ptr的控制块完全不同,因此第一次compare_exchange_weak会失败,因为ptrExpected(空)和原子变量里的过期weak_ptr不相等。 - 失败后
ptrExpected会被更新为原子变量中的过期weak_ptr,此时检查ptrExpected.expired()为真;若期间无其他线程修改原子变量,第二次调用compare_exchange_weak时,ptrExpected和原子变量内的weak_ptr指向同一控制块(哪怕已过期),因此比较会成功,原子变量会被更新为ptrDesired。
核心依据是:标准明确规定,std::atomic<std::shared_ptr<T>>和std::atomic<std::weak_ptr<T>>的原子操作,其相等性判断等价于对应的非原子shared_ptr/weak_ptr的operator==行为。而std::weak_ptr的operator==规则是:两个weak_ptr相等当且仅当它们共享同一控制块(或均为空),与对象存活状态无关。
因此你的潜在修复版本逻辑符合C++标准规范,并非编译器偶然表现。另外可以优化:将ptrExpected的声明移到while循环外,避免重复构造空weak_ptr,减少不必要的控制块交互。
内容的提问来源于stack exchange,提问作者GeoRanger

