最下面有完整的代码,不想看的可以直接跳过
今天让我们来手撕c++的共享指针shared_ptr,共享指针的是通过引用计算和RAII思想来实现的,当我们将指针赋值给其他shared ptr时内部有一个计数变量会加1,当shared ptr触发析构时这个计数就会减1,当计数为0时就会自动的释放管理的资源
这个就是shared ptr的基本原理下面我们开始实现,首先来介绍构造函数和成员变量,我们的类需要通用型肯定是要用模板的,所以我们的指针就用到了模板来满足对所有指针类型的支持,引用计数需要用原子变量来保证并发安全
template<typename T>
class SharedPtr{
public:
explicit SharedPtr() noexcept : _ptr(nullptr), _count(new std::atomic<size_t>(0)){}
explicit SharedPtr(T* ptr) : _ptr(ptr) { _count = (_ptr == nullptr) ? new std::atomic<size_t>(0) : new std::atomic<size_t>(1); }
SharedPtr(const SharedPtr<T>& other):_count(other._count), _ptr(other._ptr) {
if (_ptr != nullptr) (*_count)++;
}
SharedPtr(SharedPtr<T>&& other) noexcept : _count(other._count), _ptr(other._ptr) {
other._count = nullptr;
other._ptr = nullptr;
}
private:
std::atomic<size_t>* _count;
T* _ptr;
};
explicit关键字的作用是防止隐式转换,防止普通指针在不经意间转换成shared ptr,使用模板T作为我们内部的指针,用std::atomic原子变量来实现引用变量,这个使用指针的目的是确保多个shared ptr管理同一个资源时,他们的行为一致性,拷贝构造需要注意的是进行自增,移动构造需要注意将另一方置空
下面我们再来介绍拷贝赋值和移动赋值,以及一些运算符重载
SharedPtr<T>& operator=(const SharedPtr<T>& other) {
if (this == &other) return *this;
reset();
_count = other._count;
_ptr = other._ptr;
if (other._ptr != nullptr) (*_count)++;
return *this;
}
SharedPtr<T>& operator=(SharedPtr<T>&& other) noexcept {
if (this == &other) return *this;
reset();
_count = other._count;
_ptr = other._ptr;
other._count = nullptr;
other._ptr = nullptr;
return *this;
}
T& operator*() const noexcept {
if (_ptr == nullptr) throw std::runtime_error("nullptr dereference");
return *_ptr;
}
T* operator->() const noexcept {
if (_ptr == nullptr) throw std::runtime_error("nullptr dereference");
return _ptr;
}
bool operator==(const SharedPtr<T>& other) const noexcept {
return _ptr == other._ptr;
}
bool operator!=(const SharedPtr<T>& other) const noexcept {
return _ptr!= other._ptr;
}
*与->要确保能发出异常,reset函数的作用是重新调整自己后面我们再介绍,赋值操作一定要调用这个函数,保证能清除原来的自己
我们最后来介绍析构还有MakeShared函数
private:
// 允许 MakeShared 访问私有构造函数
template<typename U, typename... Args>
friend SharedPtr<U> MakeShared(Args&&... args);
SharedPtr(T* ptr, std::atomic<size_t>* count) noexcept : _ptr(ptr), _count(count) {}void reset() noexcept {
if (_ptr != nullptr) {
if (--(*_count) == 0) {
_ptr->~T();
::operator delete(static_cast<void*>(_count)); // 释放引用计数和对象的整块内存
}
}
else if (_count != nullptr) {
::operator delete(static_cast<void*>(_count)); // 释放引用计数和对象的整块内存
}
_ptr = nullptr;
_count = nullptr;
}
template<typename T, typename... Args>
SharedPtr<T> MakeShared(Args&&... args) {
// 使用 std::aligned_storage 来确保内存对齐
using StorageType = typename std::aligned_storage<sizeof(T) + sizeof(std::atomic<size_t>), alignof(T)>::type;
void* mem = ::operator new(sizeof(StorageType)); // 一次性分配内存
auto* count = new (mem) std::atomic<size_t>(1); // 在已分配的内存上构造引用计数
auto* obj = new (static_cast<void*>(static_cast<char*>(mem) + sizeof(std::atomic<size_t>))) T(std::forward<Args>(args)...); // placement new构造对象return SharedPtr<T>(obj, count); // 返回构造的 SharedPtr
}
reset函数会将引用计数减1并检测是否为0,为0后就清除当前的资源,MakeShared函数的作用是内存对齐并保证一次性创建内存空间
手撕shared ptr就这样完成了,希望能够帮助到大家,如果有不对的地方欢迎指正
#pragma once
template<typename T>
class SharedPtr{
public:
explicit SharedPtr() noexcept : _ptr(nullptr), _count(new std::atomic<size_t>(0)){}
explicit SharedPtr(T* ptr) : _ptr(ptr) { _count = (_ptr == nullptr) ? new std::atomic<size_t>(0) : new std::atomic<size_t>(1); }
SharedPtr(const SharedPtr<T>& other):_count(other._count), _ptr(other._ptr) {
if (_ptr != nullptr) (*_count)++;
}
SharedPtr(SharedPtr<T>&& other) noexcept : _count(other._count), _ptr(other._ptr) {
other._count = nullptr;
other._ptr = nullptr;
}SharedPtr<T>& operator=(const SharedPtr<T>& other) {
if (this == &other) return *this;
reset();_count = other._count;
_ptr = other._ptr;
if (other._ptr != nullptr) (*_count)++;
return *this;
}
SharedPtr<T>& operator=(SharedPtr<T>&& other) noexcept {
if (this == &other) return *this;
reset();_count = other._count;
_ptr = other._ptr;
other._count = nullptr;
other._ptr = nullptr;
return *this;
}T& operator*() const noexcept {
if (_ptr == nullptr) throw std::runtime_error("nullptr dereference");
return *_ptr;
}
T* operator->() const noexcept {
if (_ptr == nullptr) throw std::runtime_error("nullptr dereference");
return _ptr;
}
bool operator==(const SharedPtr<T>& other) const noexcept {
return _ptr == other._ptr;
}
bool operator!=(const SharedPtr<T>& other) const noexcept {
return _ptr!= other._ptr;
}
T* get() const noexcept {
return _ptr;
}
size_t use_count() const noexcept {
return *_count;
}~SharedPtr() {
reset();
}private:
// 允许 MakeShared 访问私有构造函数
template<typename U, typename... Args>
friend SharedPtr<U> MakeShared(Args&&... args);
SharedPtr(T* ptr, std::atomic<size_t>* count) noexcept : _ptr(ptr), _count(count) {}void reset() noexcept {
if (_ptr != nullptr) {
if (--(*_count) == 0) {
_ptr->~T();
::operator delete(static_cast<void*>(_count)); // 释放引用计数和对象的整块内存
}
}
else if (_count != nullptr) {
::operator delete(static_cast<void*>(_count)); // 释放引用计数和对象的整块内存
}
_ptr = nullptr;
_count = nullptr;
}
private:
std::atomic<size_t>* _count;
T* _ptr;
};template<typename T, typename... Args>
SharedPtr<T> MakeShared(Args&&... args) {
// 使用 std::aligned_storage 来确保内存对齐
using StorageType = typename std::aligned_storage<sizeof(T) + sizeof(std::atomic<size_t>), alignof(T)>::type;
void* mem = ::operator new(sizeof(StorageType)); // 一次性分配内存
auto* count = new (mem) std::atomic<size_t>(1); // 在已分配的内存上构造引用计数
auto* obj = new (static_cast<void*>(static_cast<char*>(mem) + sizeof(std::atomic<size_t>))) T(std::forward<Args>(args)...); // placement new构造对象return SharedPtr<T>(obj, count); // 返回构造的 SharedPtr
}