5.2.4. Операции над std::atomic<T*>: арифметика указателей
Атомарная форма указателя на тип T — std::atomic<T*> — выглядит так же, как атомарная форма bool (std::atomic<bool>). Интерфейс по существу такой же, только операции применяются к указателям на значения соответствующего типа, а не к значениям типа bool. Как и в случае std::atomic<bool>, копирующие конструктор и оператор присваивания не определены, но разрешено конструирование и присваивание на основе подходящих указателей. Помимо обязательной функции is_lock_free(), тип std::atomic<T*> располагает также функциями load(), store(), exchange(), compare_exchange_weak() и compare_exchange_strong() с такой же семантикой, как std::atomic<bool>, но принимаются и возвращаются значения типа T*, а не bool.
Новыми в типе std::atomic<T*> являются арифметические операции над указателями. Базовые операции предоставляются функциями-членами fetch_add() и fetch_sub(), которые прибавляют и вычитают целое число из сохраненного адреса, а также операторы +=, -=, ++ и -- (последние в обеих формах — пред и пост), представляющие собой удобные обертки вокруг этих функций. Операторы работают так же, как для встроенных типов: если x — указатель std::atomic<Foo*> на первый элемент массива объектов типа Foo, то после выполнения оператора x+=3 x будет указывать на четвертый элемент и при этом возвращается простой указатель Foo*, который также указывает на четвертый элемент. Функции fetch_add() и fetch_sub() отличаются от операторов тем, что возвращают старое значение (то есть x.fetch_add(3) изменит x, так что оно будет указывать на четвертый элемент, но вернет указатель на первый элемент массива). Эту операцию еще называют обменять-и-прибавить, она относится к категории атомарных операций чтения-модификации-записи, наряду с exchange(), compare_exchange_weak() и compare_exchange_strong(). Как и другие операции такого рода, fetch_add() возвращает простой указатель T*, а не ссылку на объект std::atomic<T*>, поэтому вызывающая программа может выполнять действия над прежним значением:
class Foo{};
Foo some_array[5]; │ Прибавить 2 к p
std::atomic<Foo*> p(some_array);│ и вернуть старое
Foo* x = p.fetch_add(2); ←┘ значение
assert(x == some_array);
assert(p.load() == &some_array[2]);
x = (p -= 1); ←┐ Вычесть 1 из p
assert(x == &some_array[1]); │ и вернуть новое
assert(p.load() == &some_array[1]);│ значение
Функциям можно также передать в дополнительном аргументе семантику упорядочения доступа к памяти:
p.fetch_add(3, std::memory_order_release);
Поскольку fetch_add() и fetch_sub() — операции чтения-модификации-записи, то они принимают любую семантику упорядочения и могут участвовать в последовательности освобождений. Для операторных форм задать семантику невозможно, поэтому предполагается семантика memory_order_sеq_cst.
Все прочие атомарные типы по существу одинаковы: это атомарные целочисленные типы с общим интерфейсом, различаются они только ассоциированными встроенными типами. Поэтому я опишу их все сразу.