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.

Все прочие атомарные типы по существу одинаковы: это атомарные целочисленные типы с общим интерфейсом, различаются они только ассоциированными встроенными типами. Поэтому я опишу их все сразу.