实现一个简易shared_ptr模板类需要些什么
引用计数
控制对引用计数访问权限的互斥锁
构造函数,拷贝构造函数,重载赋值运算符,解引用运算符等等
析构函数,避免在临界区访问引用计数
类的私有成员变量 1 2 3 4 5 6 7 template <class T > class my_shared_ptr {private : int * _p_ref_count; std::mutex* _p_mutex; T* _p_ptr; };
定义构造与析构函数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 template <class T >class my_shared_ptr {public : my_shared_ptr (T* ptr=nullptr ): _p_ref_count(new int (1 )), _p_mutex(new std::mutex), _p_ptr(ptr) {} my_shared_ptr (const my_shared_ptr<T>& msp) _p_ref_count(msp._p_ref_count), _p_mutex(msp._p_mutex), _p_ptr(msp._p_ptr) { add_ref_count (); } ~my_shared_ptr () { release (); } my_shared_ptr<T>& operator =(const my_shared_ptr<T>& msp) { if (_p_ptr!=msp._p_ptr) { release (); _p_ref_count = msp._p_ref_count; _p_mutex = msp._p_mutex; _p_ptr = msp._p_ptr; add_ref_count (); } return *this ; } T& operator *() { return *_p_ptr; } T* operator ->() { return _p_ptr; } T* get () { return _p_ptr; } int get_ref_count () { return *_p_ref_count; } void add_ref_count () { _p_mutex->lock (); ++(*_p_ref_count); _p_mutex->unlock (); }private : void release () { bool delete_flag = false ; _p_mutex->lock (); if (--(*_p_ref_count)==0 ) { delete _p_ref_count; delete _p_ptr; delete_flag = true ; } _p_mutex->unlock (); if (delete_flag) delete _p_mutex; } };
线程安全问题
my_shared_ptr对象中的引用计数因为使用了互斥锁所以是线程安全的
但是my_shared_ptr管理的对象存放在堆上,如果两个线程同时访问,则将造成线程安全问题
存在的问题 my_shared_ptr模板类没有考虑不是new出来的对象,实际上shared_ptr针对这种情况设计了仿函数删除器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 template <class T >struct FreeFunc { void operator () (T* ptr) { cout << "free:" << ptr << endl; free (ptr); } };template <class T >struct DeleteArrayFunc { void operator () (T* ptr) { cout << "delete[]" << ptr << endl; delete [] ptr; } };int main () { FreeFunc<int > freeFunc; std::shared_ptr<int > sp1 ((int *)malloc(4 ), freeFunc) ; DeleteArrayFunc<int > deleteArrayFunc; std::shared_ptr<int > sp2 ((int *)malloc(4 ), deleteArrayFunc) ; return 0 ; }
std::shared_ptr的线程安全问题 在哪些方面存在安全隐患
引用计数
修改指向
shared_ptr中的T的线程安全问题
std::shared_ptr中有两个指针,一个指向所管理的数据,一个指向控制块,这里面包括引用计数,weak_ptr的数量,删除器和分配器之类的,rc是存放在堆上的。
根据cppreference的说法,rc的加减是内存安全的
To satisfy thread safety requirements, the reference counters are typically incremented using an equivalent of std::atomic::fetch_add with std::memory_order_relaxed (decrementing requires stronger ordering to safely destroy the control block)
关于修改指向时的线程安全问题,依多线程访问的是不是同一个shared_ptr模板类的对象有所不同
1 2 3 4 5 std::thread td ([&sp1] () {....}) ; `` ```cpp std::thread td ([sp1] () {....}) ;
如果std::thread的回调函数是一个lambda表达式,那么如果这里是引用捕获就有问题,拷贝就没事
或者下面这种
1 2 3 4 5 6 7 8 9 10 11 void fn (shared_ptr<A>* sp) { ... } ... std::thread td (fn, &sp1) ;void fn (shared_ptr<A>& sp) { ... } ... std::thread td (fn, std::ref(sp1)) ;
当我们在多线程回调中修改指向时,就不是线程安全的了
1 2 3 4 5 6 7 8 void fn (shared_ptr<A>& sp) { ... if (..) { sp = other_sp; } else if (...) { sp = other_sp2; } }
这时other_sp的rc要加1,sp的要减1,但这整个过程并不是一个原子过程
最后,如果shared_str管理的数据是STL容器这类,那么任何多线程间修改容器结构的操作都很容易导致core dump.
unique_ptr的简易实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 template <typename T>class MyUniquePtr {public : explicit MyUniquePtr (T* ptr = nullptr ) :mPtr(ptr) { } ~MyUniquePtr () { if (mPtr) delete mPtr; } MyUniquePtr (MyUniquePtr &&p) noexcept ; MyUniquePtr& operator =(MyUniquePtr &&p) noexcept ; MyUniquePtr (const MyUniquePtr &p) = delete ; MyUniquePtr& operator =(const MyUniquePtr &p) = delete ; T& operator *() const noexcept {return *mPtr;} T* operator ->()const noexcept {return mPtr;} explicit operator bool () const noexcept {return mPtr;} void reset (T* q = nullptr ) noexcept { if (q != mPtr){ if (mPtr) delete mPtr; mPtr = q; } } T* release () noexcept { T* res = mPtr; mPtr = nullptr ; return res; } T* get () const noexcept {return mPtr;} void swap (MyUniquePtr &p) noexcept { using std::swap; swap (mPtr, p.mPtr); }private : T* mPtr; };template <typename T> MyUniquePtr<T>& MyUniquePtr<T>::operator =(MyUniquePtr &&p) noexcept { swap (*this , p); return *this ; }template <typename T> MyUniquePtr<T> :: MyUniquePtr (MyUniquePtr &&p) noexcept : mPtr (p.mPtr) { p.mPtr == NULL ; }
要注意bool运算符要是explicit的,不然判断ptr1==ptr2的时候,就会把两边都转化为bool值true,另外就是赋值运算符,用swap,因为自赋值情况是很少见的,用swap的话,不是自赋值的时候,另一个指针超出作用域会被自动析构,这样效率更高