How is std::atomic_ref implemented for non-atomic types?
An implementation can use a hash based on the address of the object to determine which of a set of locks to acquire while performing the operation.
The implementation is pretty much exactly the same as std::atomic<T>
itself. This is not a new problem.
See Where is the lock for a std::atomic? A typical implementation of std::atomic
/ std::atomic_ref
a static hash table of locks, indexed by address, for non-lock-free objects. Hash collisions only lead to extra contention, not a correctness problem. (Deadlocks are still impossible; the locks are only used by atomic functions which never try to take 2 at a time.)
On GCC for example, std::atomic_ref
is just another way to invoke __atomic_store
on an object. (See the GCC manual: atomic builtins)
The compiler knows whether T
is small enough to be lock-free or not. If not, it calls the libatomic library function which will use the lock.
(fun fact: that means it only works if the object has sufficient alignment for atomic<T>
. But on many 32-bit platforms including x86, uint64_t
might only have 4-byte alignment. atomic_ref
on such an object will compile and run, but not actually be atomic if the compiler uses an SSE 8-byte load/store in 32-bit mode to implement it. Fortunately there's no danger for objects that have alignof(T) == sizeof(T)
, like most primitive types on 64-bit architectures.)