在现代 C++ 开发中,智能指针是管理动态内存的重要工具。标准库中已经提供了 std::shared_ptr、std::unique_ptr 等实现,但在一些特定场景下,你可能需要一个更轻量或更具可定制性的共享指针。下面将展示一个最小化、易于理解的自定义 SharedPtr 实现,并讨论其在多线程环境中的使用注意事项。
1. 设计思路
- 引用计数:核心是一个共享计数器,记录指向同一资源的
SharedPtr对象数量。 - 原子操作:为了线程安全,计数器使用 `std::atomic `。
- 自定义删除器:支持用户传入自定义的析构函数,以兼容多态或非
new分配的资源。 - 基本接口:实现
operator*,operator->,get(),use_count(),reset(),swap()等常用成员。
2. 代码实现
#include <atomic>
#include <utility>
#include <iostream>
#include <cassert>
template<typename T>
class SharedPtr {
public:
// 默认构造,指向空
SharedPtr() noexcept : ptr_(nullptr), count_(nullptr), deleter_(nullptr) {}
// 从裸指针创建
explicit SharedPtr(T* p, std::function<void(T*)> deleter = std::default_delete<T>())
: ptr_(p), deleter_(std::move(deleter)) {
count_ = new std::atomic <size_t>(1);
}
// 拷贝构造
SharedPtr(const SharedPtr& other) noexcept
: ptr_(other.ptr_), count_(other.count_), deleter_(other.deleter_) {
if (count_) ++(*count_);
}
// 移动构造
SharedPtr(SharedPtr&& other) noexcept
: ptr_(other.ptr_), count_(other.count_), deleter_(std::move(other.deleter_)) {
other.ptr_ = nullptr;
other.count_ = nullptr;
}
// 拷贝赋值
SharedPtr& operator=(const SharedPtr& other) {
if (this != &other) {
release(); // 先释放自身资源
ptr_ = other.ptr_;
count_ = other.count_;
deleter_ = other.deleter_;
if (count_) ++(*count_);
}
return *this;
}
// 移动赋值
SharedPtr& operator=(SharedPtr&& other) noexcept {
if (this != &other) {
release();
ptr_ = other.ptr_;
count_ = other.count_;
deleter_ = std::move(other.deleter_);
other.ptr_ = nullptr;
other.count_ = nullptr;
}
return *this;
}
// 析构
~SharedPtr() {
release();
}
// 解引用
T& operator*() const { assert(ptr_); return *ptr_; }
T* operator->() const { assert(ptr_); return ptr_; }
T* get() const noexcept { return ptr_; }
size_t use_count() const noexcept { return count_ ? *count_ : 0; }
void reset() noexcept {
release();
ptr_ = nullptr;
count_ = nullptr;
deleter_ = nullptr;
}
void reset(T* p) {
if (ptr_ != p) {
release();
ptr_ = p;
deleter_ = std::default_delete <T>();
count_ = new std::atomic <size_t>(1);
}
}
void swap(SharedPtr& other) noexcept {
std::swap(ptr_, other.ptr_);
std::swap(count_, other.count_);
std::swap(deleter_, other.deleter_);
}
explicit operator bool() const noexcept { return ptr_ != nullptr; }
private:
void release() {
if (count_ && --(*count_) == 0) {
if (deleter_) deleter_(ptr_);
delete count_;
}
}
T* ptr_;
std::atomic <size_t>* count_;
std::function<void(T*)> deleter_;
};
3. 使用示例
struct Base { virtual void foo() { std::cout << "Base\n"; } };
struct Derived : Base { void foo() override { std::cout << "Derived\n"; } };
int main() {
SharedPtr <Base> sp1(new Derived()); // 计数为1
{
SharedPtr <Base> sp2 = sp1; // 计数变为2
std::cout << sp2.use_count() << "\n"; // 输出2
sp2->foo(); // 调用 Derived::foo
} // sp2析构,计数变为1
std::cout << sp1.use_count() << "\n"; // 输出1
// 自定义删除器(例如使用 `malloc` 分配)
SharedPtr <int> sp3(reinterpret_cast<int*>(std::malloc(sizeof(int))),
[](int* p){ std::free(p); });
*sp3 = 42;
std::cout << *sp3 << "\n"; // 输出42
return 0;
}
4. 多线程注意事项
- 计数器使用
std::atomic,实现了基本的线程安全计数增减。 - 对同一对象的并发访问(读写)仍需外部同步(如
std::mutex),因为SharedPtr本身不提供数据层面的同步。 reset()与release()的实现是原子操作,但在高并发场景下,频繁的reset()可能导致大量内存分配/释放。可考虑预先分配计数器或使用内存池。
5. 与 std::shared_ptr 的比较
| 特性 | std::shared_ptr |
自定义 SharedPtr |
|---|---|---|
| 线程安全计数 | ✅ | ✅ |
| 兼容性 | 与 STL 容器、算法完美配合 | 需要自行实现容器友好 |
| 性能 | 内置优化(如 make_shared) |
可能更轻量,但缺少优化 |
| 删除器 | 支持自定义 | 支持自定义 |
| 其他 | 友好异常安全、weak_ptr 支持 |
仅实现核心功能 |
6. 结语
自定义共享指针可以帮助你在特殊需求下精细控制内存管理、减少不必要的开销或满足教学需求。但在大多数生产环境中,建议优先使用标准库提供的 std::shared_ptr。如果你确实需要定制行为,以上实现已经具备基本的可用性,后续可以根据项目需求进一步扩展。