✏️

Smart Pointer

Tags
为了更安全容易的使用动态内存,C++11引入了三种智能指针
定义于memory库

shared_ptr

允许多个指针指向同一对象,通过一个 引用计数器(完整地说是一个控制块,见下) 来记录有多少个指针指向对象
// 简化的控制块结构 struct ControlBlock { std::atomic<size_t> strong_ref_count; // 强引用计数 std::atomic<size_t> weak_ref_count; // 弱引用计数 // 其他数据(删除器、分配器等) };
自动销毁对象内存:当shared_ptr所指对象的强引用计数降为0,则销毁对象
由于RAII的机制,在shared_ptr离开作用域后会直接递减引用计数(return时会再次增加)
默认情况下,shared_ptr只能管理「自由存储区」的对象内存,因为默认删除器调用的是其delete方法销毁内存
线程安全:shared_ptr的引用计数是安全且无锁的,但它指向的对象不是,因此可以说shared_ptr不是线程安全的
notion image
notion image
notion image
 
简单实现一个对引用计数线程安全的shared_ptr
// // Created by 罗孝俊 on 2025/4/17. // #ifndef TEST_M_SHARED_PTR_H #define TEST_M_SHARED_PTR_H #include <atomic> #include <stdexcept> template <typename T> class m_shared_ptr { private: T* _data; std::atomic<int>* _count; // decrease _count void decrease_collect() { if (_count->fetch_sub(1, std::memory_order_acq_rel) == 1) { delete _data; delete _count; } } // increase _count void increase() { _count->fetch_add(1, std::memory_order_relaxed); } public: // Constructor explicit m_shared_ptr(T* pData) { _data = pData; _count = new std::atomic<int>(pData == nullptr ? 0 : 1); } // Finalizer ~m_shared_ptr() { if (_data != nullptr) { decrease_collect(); } } // Copy constructor m_shared_ptr(const m_shared_ptr<T>& other) { if (this != &other) { _data = other._data; _count = other._count; if (_data != nullptr) { increase(); } } } // '=' override // if neq: process current _data, and copy&increase another one m_shared_ptr& operator= (const m_shared_ptr<T>& other) { if (this == &other) { return *this; } if (_data != nullptr) { decrease_collect(); } // add _data = other._data; _count = other._count; if (_data != nullptr) { increase(); } return *this; } // operator * T& operator*() { if (_data) { return *_data; } // maybe unsafe throw std::runtime_error("Dereferencing null m_shared_ptr"); } // operator -> T* operator->() { return _data; } // use_count int use_count() { return _count->load(std::memory_order_relaxed); } // unique bool unique() { return use_count() == 1; } // decrease_collect and reset the _data to arg void reset(T* arg = nullptr) { if (_data != nullptr) { decrease_collect(); } _data = arg; _count = new std::atomic<int>(_data == nullptr ? 0 : 1); } }; #endif //TEST_M_SHARED_PTR_H
 

make_shared

优势:
其实就是对比直接构造
shared_ptr<A> p1 = make_shared<A>(); shared_ptr<A> p2 = shared_ptr<A>(new A());
  • make_shared只会调用一次内存分配器,一次性分配完对象 + 智能指针控制块的空间,故性能比标准的好
  • 直接构造方法会出现一种情况:native object分配构造完毕,智能指针构造失败抛出异常
    • 这样直接导致native object内存泄漏,异常也有可能没被回收
 
// deepseek生成的简要实现 #include <memory> #include <iostream> // 简化版的make_shared实现概念 namespace detail { template<typename T> struct MakeSharedEnabler : public T { template<typename... Args> MakeSharedEnabler(Args&&... args) : T(std::forward<Args>(args)...) {} }; } template<typename T, typename... Args> std::shared_ptr<T> make_shared(Args&&... args) { // 一次性分配内存,大小 = 控制块 + 对象 auto* storage = ::operator new(sizeof(std::_Ref_count_obj<detail::MakeSharedEnabler<T>>) + sizeof(T)); try { // 在分配的内存中构造控制块和对象 auto* control_block = new (storage) std::_Ref_count_obj<detail::MakeSharedEnabler<T>>( std::forward<Args>(args)... ); // 创建shared_ptr,指向对象部分 return std::shared_ptr<T>(std::_Sptr_base<T>::_S_create(control_block)); } catch (...) { ::operator delete(storage); throw; } }
缺点:
  • make_shared分配的对象内存包含了数据和控制块,控制块被释放之后若对象还有引用计数则控制块内存无法被释放
 

unique_ptr

不存在拷贝赋值和构造函数
可通过reset来接管另一个内置指针
notion image
 

weak_ptr

解决shared_ptr循环引用导致的问题
可以用shared_ptr来构造,指向其管理的对象
构造后,不会改变对象的强引用计数器,只增加弱
notion image
 
循环引用案例
class A { public: shared_ptr<B> b; } class B { public: shared_ptr<A> a; } int main() { shared_ptr<A> pa = make_shared<A>(); shared_ptr<B> pb = make_shared<B>(); pa->b = pb; pb->a = pa; // 引用计数分别+1 return 0; // pa, pb所指对象引用计数-1,而对象指针的引用依然不为0,无法销毁A, B的内存,内存泄漏 T_T }
解决方案:成员变量改成weak_ptr,这样不会加 强引用计数