在C++多线程编程场景中,多个线程同时操作同一个共享对象的引用计数时,普通的int类型自增自减操作并非原子操作,会导致数据竞争和未定义行为。std::atomic模板类提供了原子化的操作接口,能够保证对内部数据的修改具备原子性,是实现线程安全引用计数的核心工具。

引用计数的基本逻辑
引用计数的核心思路是为每个共享对象维护一个计数器,记录当前有多少个对象或者指针正在引用该资源:
- 当有新的引用指向共享对象时,引用计数加1
- 当某个引用不再指向共享对象时,引用计数减1
- 当引用计数减到0时,释放共享对象占用的资源
使用std::atomic实现引用计数
基础实现示例
下面是一个简单的共享对象类,使用std::atomic<int>作为引用计数成员:
#include <atomic>
#include <iostream>
// 共享资源类
class SharedObject {
private:
// 原子引用计数,初始值为1,代表创建时就有一次引用
std::atomic<int> ref_count;
// 模拟共享资源的数据
int data;
public:
SharedObject(int val) : ref_count(1), data(val) {
std::cout << "SharedObject构造,初始引用计数: " << ref_count.load() << std::endl;
}
~SharedObject() {
std::cout << "SharedObject析构,数据值: " << data << std::endl;
}
// 增加引用计数
void add_ref() {
// 原子自增操作,返回自增前的值
ref_count.fetch_add(1, std::memory_order_relaxed);
}
// 减少引用计数,返回减少后的计数值
int release() {
// 原子自减操作,返回自减前的值
int old = ref_count.fetch_sub(1, std::memory_order_acq_rel);
// 如果自减前的值是1,说明自减后变为0,需要释放资源
if (old == 1) {
delete this;
}
return old - 1;
}
// 获取当前引用计数
int get_ref_count() const {
return ref_count.load(std::memory_order_relaxed);
}
int get_data() const {
return data;
}
};
// 智能指针类,管理SharedObject的生命周期
class SharedPtr {
private:
SharedObject* ptr;
public:
// 构造函数,接管对象所有权
SharedPtr(SharedObject* p) : ptr(p) {}
// 拷贝构造函数,引用计数加1
SharedPtr(const SharedPtr& other) : ptr(other.ptr) {
if (ptr) {
ptr->add_ref();
}
}
// 赋值运算符重载
SharedPtr& operator=(const SharedPtr& other) {
if (this != &other) {
// 先释放当前指向的对象
if (ptr) {
ptr->release();
}
ptr = other.ptr;
if (ptr) {
ptr->add_ref();
}
}
return *this;
}
// 析构函数,引用计数减1
~SharedPtr() {
if (ptr) {
ptr->release();
}
}
SharedObject* get() const {
return ptr;
}
};
int main() {
SharedObject* obj = new SharedObject(100);
std::cout << "初始引用计数: " << obj->get_ref_count() << std::endl;
SharedPtr p1(obj);
std::cout << "p1接管后引用计数: " << obj->get_ref_count() << std::endl;
{
SharedPtr p2 = p1;
std::cout << "p2拷贝后引用计数: " << obj->get_ref_count() << std::endl;
}
// p2析构,引用计数减1
std::cout << "p2析构后引用计数: " << obj->get_ref_count() << std::endl;
return 0;
}
内存顺序的选择
std::atomic操作可以指定内存顺序参数,不同的内存顺序会影响操作的同步和排序效果:
std::memory_order_relaxed:最宽松的内存顺序,只保证操作原子性,不保证同步关系,适合只需要计数准确不需要同步其他内存访问的场景std::memory_order_acq_rel:获取-释放语义,适合引用计数增减的场景,能够保证释放操作和获取操作之间的同步,避免资源提前释放的问题std::memory_order_seq_cst:顺序一致语义,是最严格的内存顺序,所有使用该顺序的原子操作全局有序,性能开销相对较大
实现注意事项
避免计数溢出
引用计数的类型如果是有符号整数,当引用数量超过最大值时会出现溢出问题,因此建议使用无符号整数类型作为std::atomic的模板参数,比如std::atomic<unsigned int>。
防止重复释放
引用计数减到0时,必须确保只释放一次资源,上述示例中通过判断fetch_sub的返回值是否为1来保证只有最后一个引用释放时才调用delete,避免重复释放的问题。
线程安全的构造与析构
共享对象的构造函数和析构函数本身不是线程安全的,因此需要保证在引用计数不为0的情况下,不会有其他线程触发对象的析构操作,std::atomic的原子操作能够保证计数修改的线程安全,从而间接保证析构时机的正确性。
适用场景
std::atomic实现的引用计数适合多线程环境下共享资源的生命周期管理,比如多线程共享的配置对象、缓存对象等。如果需要更通用的智能指针功能,也可以参考上述实现思路,或者直接使用标准库的std::shared_ptr,其底层也是基于类似的原子引用计数机制实现的。
std::atomic引用计数C++多线程内存管理修改时间:2026-06-30 13:45:18