std::atomic<int>: Difference between x.fetch_add(1) and x++;
The difference is definitely not about the safety = atomicity which is guaranteed for both methods.
The most important difference I think is that fetch_add()
can take a different memory order argument while for increment operator it is always memory_order_seq_cst
.
Another obvious difference is that fetch_add()
can take not only 1
as argument while on the other hand, operator++
is more likely to be implemented using lock inc
instruction (though, theoretically nothing prevents a compiler from such an optimization for fetch_add(1)
as well)
So answering your exact question, there is no any semantically important difference between x++
and x.fetch_add(1)
. The doc says:
This function behaves as if atomic::fetch_add was called with 1 and memory_order_seq_cst as arguments.
x.fetch_add(1)
and x++
are exactly the same
If you believe cppreference, https://en.cppreference.com/w/cpp/atomic/atomic/operator_arith says:
T operator++() volatile noexcept; (1) T* operator++() volatile noexcept; (2)
1) Performs atomic pre-increment. Equivalent to
fetch_add(1)+1
.2) Performs atomic post-increment. Equivalent to
fetch_add(1)
.
https://en.cppreference.com/w/cpp/atomic/atomic/fetch_add then documents:
T fetch_add( T arg, std::memory_order order = std::memory_order_seq_cst ) noexcept;
so we see that the std::memory_order
of operator++
defaults to std::memory_order_seq_cst
, which is the stronger one available, see also: What do each memory_order mean?
C++11 standard quotes
If you don't believe cppreference, the C++11 N3337 draft 29.6.5/33 "Requirements for operations on atomic types" says:
C A ::operator++(int) volatile noexcept; C A ::operator++(int) noexcept;
Returns: fetch_add(1)
29.6.5/2 clarifies C
and A
:
- an A refers to one of the atomic types.
- a C refers to its corresponding non-atomic type
I wasn't able to find it explained clearly but I suppose Returns: fetch_add(1)
implies that fetch_add(1)
is called for its side effect of course.
It is also worth looking at the prefix version a bit further:
C A ::operator++() volatile noexcept; C A ::operator++() noexcept;
Effects: fetch_add(1)
Returns: fetch_add(1) + 1
which indicates that this one returns the value + 1 like the regular prefix increment for integers.
GCC 4.8
libstdc++-v3/include/std/atomic says atomic<int>
inherits __atomic_base<int>
:
struct atomic<int> : __atomic_base<int>
libstdc++-v3/include/bits/atomic_base.h implements it like:
__int_type
operator++(int) noexcept
{ return fetch_add(1); }
__int_type
operator++(int) volatile noexcept
{ return fetch_add(1); }
__int_type
operator++() noexcept
{ return __atomic_add_fetch(&_M_i, 1, memory_order_seq_cst); }
__int_type
operator++() volatile noexcept
{ return __atomic_add_fetch(&_M_i, 1, memory_order_seq_cst); }
_GLIBCXX_ALWAYS_INLINE __int_type
fetch_add(__int_type __i,
memory_order __m = memory_order_seq_cst) noexcept
{ return __atomic_fetch_add(&_M_i, __i, __m); }
_GLIBCXX_ALWAYS_INLINE __int_type
fetch_add(__int_type __i,
memory_order __m = memory_order_seq_cst) volatile noexcept
{ return __atomic_fetch_add(&_M_i, __i, __m); }
I do not understand why the postfix calls the fetch_add
helper and the prefix uses the built-in directly, but in the end they all come down to the GCC intrinsics __atomic_fetch_add
and __atomic_add_fetch
which do the real work.