std::atomic 初始化必须显式,如 std::atomic x{0};load/store 需显式指定 memory_order;fetch_add 等不防 ABA;atomic_ref 仅 C++20 支持且限 trivially copyable 类型。

atomic 的初始化必须显式,不能依赖默认构造
绝大多数 std::atomic 类型(如 std::atomic_int、std::atomic)**没有默认构造函数**。直接声明 std::atomic 会导致编译错误,常见报错是 call to implicitly-deleted default constructor。
正确做法是显式初始化:
-
std::atomic(推荐,C++11 起支持)x{0}; -
std::atomic(C++11 兼容写法,但 C++20 已弃用)x = ATOMIC_VAR_INIT(0); - 类成员中需在构造函数初始化列表里赋初值:
MyClass() : counter{0} {}
漏掉初始化不仅编译失败,还容易误以为“和普通变量一样可默认为零”——这是无锁代码里隐蔽的崩溃源头。
load/store 必须指定 memory_order,别偷懒用默认
load() 和 store() 默认使用 std::memory_order_seq_cst(顺序一致性),性能开销最大。实际场景中,多数读写并不需要全局严格顺序。
立即学习“C++免费学习笔记(深入)”;
常见合理选择:
- 纯计数器累加(如引用计数):
counter.fetch_add(1, std::memory_order_relaxed)—— 无同步需求,最快 - 生产者-消费者模型中的 flag 通知:
ready.store(true, std::memory_order_release)配合ready.load(std::memory_order_acquire)—— 保证 store 前的写操作对 load 可见 - 需要强顺序的控制变量(如全局开关):才用
std::memory_order_seq_cst
滥用 seq_cst 会让原子操作慢 2–5 倍(尤其在 ARM/AArch64 上),而用错 order(比如该用 acquire 却用了 relaxed)会导致罕见的数据竞争,复现困难。
fetch_add 等复合操作不是“万能安全”,仍要防 ABA 问题
fetch_add、compare_exchange_weak 这类操作本身是原子的,但它们**不解决逻辑层面的 ABA 问题**。典型例子:一个指针被释放后又恰好分配到同一地址,compare_exchange_weak 会误判为“没变过”,导致链表操作出错。
规避方式取决于场景:
- 整数计数器(如 refcount):ABA 不影响语义,可直接用
fetch_add - 指针或结构体操作:需额外版本号(如
std::atomic拆高低 32 位为指针+counter) -
标准库无直接支持,得自己封装;不要试图靠
compare_exchange_strong解决 ABA —— 它只重试,不防地址复用
很多教程只讲 “compare_exchange_weak 是原子的”,却跳过 ABA 的实际触发条件(内存池、对象复用),结果上线后偶发 crash,查一周才发现是地址被重用了。
atomic_ref 在 C++20 才可用,且仅限 trivially copyable 类型
想对已有数组元素或结构体字段做原子操作?C++20 引入了 std::atomic_ref,但它有硬性限制:
- T 必须是
trivially_copyable(如int、float、POD 结构),含虚函数、非平凡构造/析构的类不行 - 引用对象生命周期内不能被移动或销毁,否则行为未定义
- 对齐要求严格:例如
atomic_ref要求所引用的int地址满足alignof(int),数组若用char buf[1024]直接 reinterpret_cast,大概率崩
实践中更稳妥的做法:把需要原子访问的字段单独拎成 std::atomic 成员,而不是事后套 atomic_ref。后者适合高性能数值计算场景(如并行 reduce 数组),但调试成本高,新手慎用。











