在 C++ 中,智能指针是管理资源生命周期的重要工具。标准库已经提供了 std::unique_ptr、std::shared_ptr 等实现,但在某些场景下,可能需要自己实现一个支持多线程安全的引用计数智能指针。下面将从设计思路、核心实现到使用示例,逐步介绍如何编写一个简易但线程安全的 MySharedPtr。
1. 设计目标
| 功能 | 说明 |
|---|---|
| 引用计数 | 使用 std::atomic<std::size_t> 维护计数,避免锁 |
| 线程安全 | 对计数的增减均为原子操作,构造/析构时无数据竞争 |
| 自定义删除器 | 支持传入删除器,用于自定义资源释放方式 |
| 可复制/可移动 | 复制时计数递增,移动后源对象失效 |
| 弱引用 | 提供 MyWeakPtr,支持 lock() 获取有效指针 |
2. 基础实现
#include <atomic>
#include <utility>
#include <iostream>
template <typename T>
class MySharedPtr;
template <typename T>
class MyWeakPtr;
template <typename T>
class ControlBlock {
public:
std::atomic<std::size_t> use_count{1};
std::atomic<std::size_t> weak_count{0};
T* ptr;
void (*deleter)(T*) = nullptr;
ControlBlock(T* p, void (*del)(T*) = nullptr)
: ptr(p), deleter(del) {}
void release() {
if (ptr && deleter) deleter(ptr);
delete ptr;
}
};
template <typename T>
class MySharedPtr {
public:
MySharedPtr() noexcept : cb(nullptr) {}
explicit MySharedPtr(T* ptr, void (*del)(T*) = nullptr)
: cb(ptr ? new ControlBlock <T>(ptr, del) : nullptr) {}
MySharedPtr(const MySharedPtr& other) noexcept
: cb(other.cb) {
increment();
}
MySharedPtr(MySharedPtr&& other) noexcept
: cb(other.cb) {
other.cb = nullptr;
}
~MySharedPtr() {
decrement();
}
MySharedPtr& operator=(const MySharedPtr& other) noexcept {
if (this != &other) {
decrement();
cb = other.cb;
increment();
}
return *this;
}
MySharedPtr& operator=(MySharedPtr&& other) noexcept {
if (this != &other) {
decrement();
cb = other.cb;
other.cb = nullptr;
}
return *this;
}
T* get() const noexcept { return cb ? cb->ptr : nullptr; }
T& operator*() const noexcept { return *get(); }
T* operator->() const noexcept { return get(); }
std::size_t use_count() const noexcept {
return cb ? cb->use_count.load(std::memory_order_acquire) : 0;
}
bool unique() const noexcept { return use_count() == 1; }
// For weak pointer creation
friend class MyWeakPtr <T>;
private:
ControlBlock <T>* cb;
void increment() {
if (cb) cb->use_count.fetch_add(1, std::memory_order_acq_rel);
}
void decrement() {
if (cb) {
if (cb->use_count.fetch_sub(1, std::memory_order_acq_rel) == 1) {
cb->release();
if (cb->weak_count.load(std::memory_order_acquire) == 0) {
delete cb;
}
}
}
}
};
template <typename T>
class MyWeakPtr {
public:
MyWeakPtr() noexcept : cb(nullptr) {}
MyWeakPtr(const MySharedPtr <T>& shared) noexcept
: cb(shared.cb) {
increment();
}
MyWeakPtr(const MyWeakPtr& other) noexcept
: cb(other.cb) {
increment();
}
MyWeakPtr(MyWeakPtr&& other) noexcept
: cb(other.cb) {
other.cb = nullptr;
}
~MyWeakPtr() {
decrement();
}
MyWeakPtr& operator=(const MyWeakPtr& other) noexcept {
if (this != &other) {
decrement();
cb = other.cb;
increment();
}
return *this;
}
MyWeakPtr& operator=(MyWeakPtr&& other) noexcept {
if (this != &other) {
decrement();
cb = other.cb;
other.cb = nullptr;
}
return *this;
}
MySharedPtr <T> lock() const noexcept {
if (!cb) return MySharedPtr <T>();
std::size_t curr = cb->use_count.load(std::memory_order_acquire);
while (curr != 0) {
if (cb->use_count.compare_exchange_weak(
curr, curr + 1,
std::memory_order_acq_rel,
std::memory_order_acquire)) {
return MySharedPtr <T>(cb);
}
}
return MySharedPtr <T>();
}
bool expired() const noexcept {
return !cb || cb->use_count.load(std::memory_order_acquire) == 0;
}
std::size_t use_count() const noexcept {
return cb ? cb->use_count.load(std::memory_order_acquire) : 0;
}
private:
ControlBlock <T>* cb;
void increment() {
if (cb) cb->weak_count.fetch_add(1, std::memory_order_acq_rel);
}
void decrement() {
if (cb) {
if (cb->weak_count.fetch_sub(1, std::memory_order_acq_rel) == 1) {
if (cb->use_count.load(std::memory_order_acquire) == 0) {
delete cb;
}
}
}
}
};
关键点说明
-
原子引用计数
std::atomic<std::size_t>保证了在多线程环境下计数的正确性,无需加锁。 -
删除器
默认使用delete,但用户可以传入自定义删除器,适用于malloc/free、new[]/delete[]或自定义资源。 -
弱指针实现
MyWeakPtr与标准库实现保持一致,支持lock()获取强引用,并且在引用计数归零后才释放控制块。
3. 使用示例
#include <thread>
#include <vector>
struct Resource {
int id;
Resource(int x) : id(x) { std::cout << "Resource " << id << " constructed\n"; }
~Resource() { std::cout << "Resource " << id << " destructed\n"; }
};
int main() {
MySharedPtr <Resource> sp1(new Resource(42));
std::cout << "use_count: " << sp1.use_count() << '\n'; // 1
{
MySharedPtr <Resource> sp2 = sp1;
std::cout << "After copy, use_count: " << sp1.use_count() << '\n'; // 2
MyWeakPtr <Resource> wp = sp1;
std::cout << "Weak use_count: " << wp.use_count() << '\n'; // 2
// 多线程访问
std::vector<std::thread> threads;
for (int i = 0; i < 5; ++i) {
threads.emplace_back([sp1]() {
std::this_thread::sleep_for(std::chrono::milliseconds(10));
std::cout << "Thread got id: " << sp1->id << '\n';
});
}
for (auto& t : threads) t.join();
}
std::cout << "After block, use_count: " << sp1.use_count() << '\n'; // 1
// 当 sp1 失效时,Resource 会被析构
return 0;
}
运行结果示例:
Resource 42 constructed
use_count: 1
After copy, use_count: 2
Weak use_count: 2
Thread got id: 42
Thread got id: 42
Thread got id: 42
Thread got id: 42
Thread got id: 42
After block, use_count: 1
Resource 42 destructed
4. 进一步优化
| 优化方向 | 说明 |
|---|---|
| 内存池 | 在 ControlBlock 与对象共置,减少分配次数 |
| 锁优化 | 对于读多写少场景,可使用 std::shared_mutex 与读写锁 |
| 自定义内存分配器 | 通过模板参数注入分配器,兼容多种内存管理策略 |
| 异常安全 | 在构造时若分配失败抛异常,确保没有泄漏 |
小结
通过上述实现,我们获得了一个轻量、线程安全、支持自定义删除器的智能指针。虽然标准库已经提供了 std::shared_ptr 与 std::weak_ptr,但在需要对内部细节做特殊控制或嵌入自定义资源管理时,自定义实现可以提供更大的灵活性。希望本文能帮助你更深入地理解智能指针的内部工作原理,并为自己的项目提供参考。