在 C++ 标准库中,std::shared_ptr 与 std::unique_ptr 已经非常成熟,但在某些特定场景下我们可能需要一个更轻量或更定制化的智能指针,例如:
- 只需要弱引用(类似
std::weak_ptr)但不想额外维护引用计数。 - 对引用计数实现需要自定义内存分配策略。
- 需要在多线程环境下保证线程安全,但不想使用
std::atomic过多。
下面给出一种基于原子计数的自定义 ThreadSafeSmartPtr 的实现示例,并说明其线程安全机制、内存管理细节以及如何使用。
1. 设计目标
| 需求 | 说明 |
|---|---|
| 线程安全 | 所有对引用计数的增减操作必须原子化。 |
| 轻量级 | 只保留最少必要的成员:指向对象的裸指针、原子引用计数。 |
| 自定义析构函数 | 允许用户在创建指针时提供自定义的销毁策略。 |
| 可与 STL 兼容 | 支持 operator*, operator->, operator bool 等。 |
| 无循环引用 | 仅提供单向引用计数,避免循环引用问题。 |
2. 基础实现
#include <atomic>
#include <functional>
#include <utility>
#include <iostream>
template <typename T>
class ThreadSafeSmartPtr {
public:
// 构造函数
explicit ThreadSafeSmartPtr(T* ptr = nullptr,
std::function<void(T*)> deleter = nullptr)
: ptr_(ptr), deleter_(std::move(deleter)), ref_count_(nullptr)
{
if (ptr_) {
// 创建一个新的引用计数
ref_count_ = new std::atomic <size_t>(1);
}
}
// 拷贝构造
ThreadSafeSmartPtr(const ThreadSafeSmartPtr& other)
: ptr_(other.ptr_), deleter_(other.deleter_), ref_count_(other.ref_count_)
{
increment();
}
// 移动构造
ThreadSafeSmartPtr(ThreadSafeSmartPtr&& other) noexcept
: ptr_(other.ptr_), deleter_(std::move(other.deleter_)),
ref_count_(other.ref_count_)
{
other.ptr_ = nullptr;
other.ref_count_ = nullptr;
}
// 赋值运算符
ThreadSafeSmartPtr& operator=(ThreadSafeSmartPtr other) noexcept {
swap(other);
return *this;
}
// 析构
~ThreadSafeSmartPtr() {
decrement();
}
// 访问
T& operator*() const { return *ptr_; }
T* operator->() const { return ptr_; }
T* get() const noexcept { return ptr_; }
explicit operator bool() const noexcept { return ptr_ != nullptr; }
// 用于调试
size_t use_count() const noexcept {
return ref_count_ ? ref_count_->load(std::memory_order_relaxed) : 0;
}
void reset(T* ptr = nullptr, std::function<void(T*)> deleter = nullptr) {
ThreadSafeSmartPtr newPtr(ptr, std::move(deleter));
swap(newPtr);
}
private:
void increment() noexcept {
if (ref_count_) {
ref_count_->fetch_add(1, std::memory_order_relaxed);
}
}
void decrement() noexcept {
if (ref_count_ && ref_count_->fetch_sub(1, std::memory_order_acq_rel) == 1) {
// 计数归零,删除对象
if (deleter_) deleter_(ptr_);
else delete ptr_;
delete ref_count_;
}
}
void swap(ThreadSafeSmartPtr& other) noexcept {
std::swap(ptr_, other.ptr_);
std::swap(deleter_, other.deleter_);
std::swap(ref_count_, other.ref_count_);
}
T* ptr_;
std::function<void(T*)> deleter_;
std::atomic <size_t>* ref_count_;
};
关键点说明
-
引用计数实现
- 使用 `std::atomic ` 进行计数,保证 `fetch_add` 与 `fetch_sub` 的原子性。
memory_order_relaxed适用于大多数情况;memory_order_acq_rel在decrement的fetch_sub里确保析构顺序。
-
自定义析构
- 构造函数接受
std::function<void(T*)>,如果用户未提供则默认使用delete。 - 这为需要自定义回收策略(如内存池、对象池、网络资源等)提供灵活性。
- 构造函数接受
-
拷贝与移动
- 拷贝构造时先增加计数;移动构造时将指针转移,避免不必要的计数变化。
- 赋值运算符使用“复制并交换”(copy‑and‑swap)模式,简化异常安全。
-
使用场景
- 与
std::shared_ptr类似,但更轻量(无control_block对象),适合对性能极致要求的高频指针操作。 - 在多线程环境下,所有计数变更均为原子操作,确保无数据竞争。
- 与
3. 示例使用
#include <thread>
#include <vector>
struct Foo {
int value;
~Foo() { std::cout << "Foo destroyed\n"; }
};
int main() {
ThreadSafeSmartPtr <Foo> p(new Foo{42});
std::cout << "Initial use_count: " << p.use_count() << '\n';
// 多线程测试
std::vector<std::thread> threads;
for (int i = 0; i < 10; ++i) {
ThreadSafeSmartPtr <Foo> local = p; // 拷贝,计数加一
threads.emplace_back([local] {
std::this_thread::sleep_for(std::chrono::milliseconds(100));
std::cout << "Thread use_count: " << local.use_count() << '\n';
});
}
for (auto& t : threads) t.join();
std::cout << "After threads, use_count: " << p.use_count() << '\n';
return 0;
}
输出示例(顺序可能略有不同):
Initial use_count: 1
Thread use_count: 11
Thread use_count: 11
...
After threads, use_count: 1
Foo destroyed
4. 进一步扩展
-
弱引用
可以在同一个ThreadSafeSmartPtr内部再加一个std::weak_ptr(或自定义)来实现弱引用,避免循环引用。 -
自定义内存分配
通过在deleter_中使用内存池(如std::pmr::memory_resource)来实现统一的内存回收。 -
可观测性
在use_count()前后添加日志或事件,以便调试复杂并发程序。 -
与
std::allocator结合
在构造函数中接受Allocator参数,用于分配对象与计数块。
5. 小结
- 本实现提供了一个 线程安全、轻量、可自定义析构 的智能指针模板。
- 它兼容大多数 STL 容器与算法,使用方式类似
std::shared_ptr。 - 通过
std::atomic确保多线程安全,同时保持了简洁的实现逻辑。 - 若需更高级特性(如循环引用检测、弱引用、可观测性等),可以在此基础上继续扩展。