题目
实现一个线程安全的引用计数智能指针
信息
- 类型:问答
- 难度:⭐⭐
考点
RAII原则,拷贝控制,移动语义,线程安全,运算符重载
快速回答
实现线程安全的引用计数智能指针需要:
- 使用
std::atomic保证引用计数的原子操作 - 遵循RAII原则管理资源生命周期
- 正确实现拷贝构造/赋值和移动构造/赋值
- 重载
operator*和operator->提供指针语义 - 处理自赋值和异常安全问题
核心原理
引用计数智能指针通过计数器跟踪资源引用数,当计数归零时自动释放资源。线程安全需保证:
- 引用计数的增减操作是原子的
- 资源释放只发生一次
- 避免拷贝时的竞态条件
代码实现
template<typename T>
class SafePtr {
T* ptr;
std::atomic<size_t>* count;
void release() {
if (count && --(*count) == 0) {
delete ptr;
delete count;
}
}
public:
explicit SafePtr(T* p = nullptr)
: ptr(p), count(p ? new std::atomic<size_t>(1) : nullptr) {}
// 拷贝构造
SafePtr(const SafePtr& other)
: ptr(other.ptr), count(other.count) {
if (count) (*count)++;
}
// 移动构造
SafePtr(SafePtr&& other) noexcept
: ptr(other.ptr), count(other.count) {
other.ptr = nullptr;
other.count = nullptr;
}
// 拷贝赋值
SafePtr& operator=(const SafePtr& other) {
if (this != &other) {
release(); // 释放原资源
ptr = other.ptr;
count = other.count;
if (count) (*count)++;
}
return *this;
}
// 移动赋值
SafePtr& operator=(SafePtr&& other) noexcept {
if (this != &other) {
release();
ptr = other.ptr;
count = other.count;
other.ptr = nullptr;
other.count = nullptr;
}
return *this;
}
~SafePtr() { release(); }
// 指针运算符重载
T& operator*() const { return *ptr; }
T* operator->() const { return ptr; }
size_t use_count() const {
return count ? count->load() : 0;
}
};最佳实践
- 使用
std::atomic确保计数器操作的原子性 - 移动操作使用
noexcept保证异常安全 - 赋值运算符处理自赋值情况
- 空指针安全处理(
count为nullptr时跳过操作)
常见错误
- 未实现移动语义导致不必要的拷贝开销
- 计数器非原子操作导致线程竞争
- 自赋值未检查引发资源提前释放
- 未处理空指针导致段错误
- 异常安全不足(如构造函数失败时资源泄漏)
扩展知识
- 循环引用问题:当两个对象相互持有时会导致内存泄漏,可通过
weak_ptr解决 - 删除器定制:标准库
shared_ptr支持自定义删除器处理特殊资源 - 线程安全级别:标准智能指针保证控制块线程安全,但被管理对象需额外同步
- 性能影响:原子操作比非原子操作慢2-10倍,需根据场景选择
- C++17改进:
shared_ptr支持数组类型,避免额外定义删除器