const成员函数不保证线程安全,它仅承诺不修改对象的直接、非mutable数据成员;线程安全取决于是否对共享状态正确同步,与const无关。

const 成员函数不保证线程安全
它只承诺不修改 this 指向的对象的**直接、非 mutable 的数据成员**,和你有没有多线程完全无关。C++ 标准里 const 和线程安全之间没有任何绑定关系——这是最常见的误解源头。
常见错误现象:std::vector::size() 是 const 成员函数,但如果你在另一个线程里调用 push_back(),它内部可能重分配内存、改写 size 字段甚至指针,此时并发调用 size() 就可能读到撕裂值或触发未定义行为。
-
const锁定的是“逻辑可修改性”,不是“内存可见性”或“访问互斥” - 即使所有成员都是
const,只要存在共享状态(比如静态变量、全局缓存、mutable std::mutex),就仍需同步 - 编译器不会因为函数是
const就插入内存屏障或加锁
mutable 成员是 const 函数里唯一的“逃生通道”
当你需要在 const 成员函数里做线程安全操作(比如缓存、日志、引用计数),必须把相关状态声明为 mutable,否则编译不过。
使用场景:实现线程安全的懒初始化、带锁的只读查询、调试计数器等。
立即学习“C++免费学习笔记(深入)”;
-
mutable std::mutex mtx_;允许在const函数里调用mtx_.lock() -
mutable std::shared_ptr<cache> cache_;</cache>可在const函数中更新缓存内容 - 但注意:
mutable不自动提供线程安全——你仍得自己加锁、用原子操作或保证无竞争
示例:
class Data {
mutable std::mutex mtx_;
mutable int cached_value_ = -1;
bool computed_ = false;
public:
int get() const {
std::lock_guard<std::mutex> lk(mtx_);
if (!computed_) {
cached_value_ = heavy_computation();
computed_ = true;
}
return cached_value_;
}
};
const_cast + mutable 不等于线程安全
有人试图绕过 const 限制,在 const 函数里用 const_cast 强转掉 this 的 const 性,再修改普通成员——这不仅危险,而且在多线程下极易出问题。
错误原因:这种写法破坏了 const 接口的契约,其他代码会假设该对象在 const 调用后状态不变;同时,普通成员没有内存序约束,写入可能对其他线程不可见,或被编译器重排。
- 禁止对非
mutable成员用const_cast修改,尤其在多线程环境 -
const_cast后的写入若未配合std::atomic或锁,属于数据竞争,触发未定义行为 - 即便加了锁,也违背了 const 函数的语义预期,后续维护者很难察觉隐藏副作用
真正决定线程安全的是同步机制,不是 const
一个函数是否线程安全,取决于它是否对共享状态做了正确同步,和它是不是 const 没有因果关系。你可以有线程安全的非 const 函数(比如带锁的 push()),也可以有线程不安全的 const 函数(比如裸读共享 int)。
- 判断依据只有两个:
是否访问共享状态+是否用锁 / 原子 / 无竞争方式保护 - const 成员函数如果只读局部变量、参数或纯函数计算,天然线程安全;一旦碰了全局、静态、堆上或对象内非 mutable 成员,就必须额外分析
- 标准库中多数 const 成员函数(如
std::string::c_str()、std::vector::at())仅当对象本身不被其他线程修改时才是线程安全的
容易被忽略的一点:const 对象本身不能被修改,但它的 const 成员函数仍可能通过指针、引用、静态变量间接影响外部状态——这种“隐式共享”最难排查。









