b.compare_exchange_weak(expected, true,
memory_order_acq_rel, memory_order_acquire);
b.compare_exchange_weak(expected, true, memory_order_acq_rel);
К чему приводит задание того или иного упорядочения, я расскажу в разделе 5.3.
Еще одно отличие std::atomic от std::atomic_flag заключается в том, что тип std::atomic не обязательно свободен от блокировок; для обеспечения атомарности реализация библиотеки может захватывать внутренний мьютекс. В тех редких случаях, когда это важно, можно с помощью функции-члена is_lock_free() узнать, являются ли операции над std::atomic свободными от блокировок. Это еще одна особенность, присущая всем атомарным типам, кроме std::atomic_flag.
Следующими по простоте являются атомарные специализации указателей std::atomic.
5.2.4. Операции над std::atomic: арифметика указателей
Атомарная форма указателя на тип T — std::atomic — выглядит так же, как атомарная форма bool (std::atomic). Интерфейс по существу такой же, только операции применяются к указателям на значения соответствующего типа, а не к значениям типа bool. Как и в случае std::atomic, копирующие конструктор и оператор присваивания не определены, но разрешено конструирование и присваивание на основе подходящих указателей. Помимо обязательной функции is_lock_free(), тип std::atomic располагает также функциями load(), store(), exchange(), compare_exchange_weak() и compare_exchange_strong() с такой же семантикой, как std::atomic, но принимаются и возвращаются значения типа T*, а не bool.
Новыми в типе std::atomic являются арифметические операции над указателями. Базовые операции предоставляются функциями-членами fetch_add() и fetch_sub(), которые прибавляют и вычитают целое число из сохраненного адреса, а также операторы +=, -=, ++ и -- (последние в обеих формах — пред и пост), представляющие собой удобные обертки вокруг этих функций. Операторы работают так же, как для встроенных типов: если x — указатель std::atomic на первый элемент массива объектов типа 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, поэтому вызывающая программа может выполнять действия над прежним значением:
class Foo{};
Foo some_array[5]; │Прибавить 2 к p
std::atomicи вернуть старое
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.
Все прочие атомарные типы по существу одинаковы: это атомарные целочисленные типы с общим интерфейсом, различаются они только ассоциированными встроенными типами. Поэтому я опишу их все сразу.
5.2.5. Операции над стандартными атомарными целочисленными типами