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.