std::shared_ptr
shared_ptr 是更靠谱的并且支持拷贝的智能指针。shared_ptr 的原理:是通过引用计数的方式来实现多个shared_ptr 对象之间共享资源。
shared_ptr 在其内部,给每个资源都维护了着一份计数,用来记录该份资源被几个对象共享。
在对象被销毁时(也就是调用析构函数),就说明自己不使用该资源了,对象的引用计数减 一。
如果引用计数是 0,就说明自己是最后一个使用该资源的对象,必须释放该资源;
如果引用计数不是 0,就说明除了自己还有其他对象在使用该份资源,不能释放该资源,否则其他对象就成野指针了。
share_ptr 的演示使用
注:以下的实现方式是错误的。
namespace Joy { template<class T> class shared_ptr { public: shared_ptr(T* ptr = nullptr) : _ptr(ptr) { ++_count; } ~shared_ptr() { if (--_count == 0 && _ptr) { cout << "Delete:" << _ptr << endl; delete _ptr; } } shared_ptr(shared_ptr<T>& sp) : _ptr(sp._ptr) { ++_count; } T& operator*() { return *_ptr; } T* operator->() { return _ptr; } int* GetCount() const { return &_count; } private: T* _ptr; static int _count; // 引用计数 }; template<class T> int shared_ptr<T>::_count = 0; void SharedPtrTest1() { Joy::shared_ptr<A> sp1(new A); Joy::shared_ptr<A> sp2(sp1); Joy::shared_ptr<A> sp3(sp2); Joy::shared_ptr<int> sp4(new int); Joy::shared_ptr<A> sp5(new A); Joy::shared_ptr<A> sp6(sp5); } }
shared_ptr 的模拟实现
namespace Joy { template<class T> class shared_ptr { public: shared_ptr(T* ptr = nullptr) : _ptr(ptr) , _pCount(new int(1)) {} shared_ptr(const shared_ptr<T>& sp) : _ptr(sp._ptr) , _pCount(sp._pCount) { ++(*_pCount); } void Release() { // 减减对象的计数,如果是最后一个对象,要释放资源 if (--(*_pCount) == 0) { cout << "Delete:" << _ptr << endl; delete _ptr; delete _pCount; } } ~shared_ptr() { Release(); } shared_ptr<T>& operator=(const shared_ptr<T>& sp) { // 通过_ptr来判断管理的资源是否是同一个。如果是同一个, // _ptr相等,直接返回即可 //if(this == &sp) if (_ptr == sp._ptr) { return *this; } Release(); // 减减被赋值对象的计数,如果是最后一个对象,要释放资源 // 共管新资源,++计数 _ptr = sp._ptr; _pCount = sp._pCount; ++(*_pCount); return *this; } T& operator*() { return *_ptr; } T* operator->() { return _ptr; } int* GetCount() const { return _pCount; } private: T* _ptr; int* _pCount; // 引用计数 }; void SharedPtrTest2() { Joy::shared_ptr<A> sp1(new A); Joy::shared_ptr<A> sp2(sp1); Joy::shared_ptr<A> sp3(sp2); cout << sp1.GetCount() << endl; cout << sp2.GetCount() << endl; cout << sp3.GetCount() << endl; cout << "--------" << endl; Joy::shared_ptr<int> sp4(new int); cout << sp4.GetCount() << endl; cout << "--------" << endl; Joy::shared_ptr<A> sp5(new A); Joy::shared_ptr<A> sp6(sp5); cout << sp5.GetCount() << endl; cout << sp6.GetCount() << endl; cout << "--------" << endl; } }
注:shared_ptr 的赋值运算符重载需要防止自己给自己赋值的情况。只要两个对象管理的资源是一样的,那么这两个对象就不要相互赋值,直接 return 即可。我们可以通过 _ptr 或者 _pCount 是否相等来判断是否管理同一个资源。
std::shared_ptr的循环引用
struct Node { int _val; // 注:Node*和std::shared_ptr<Node>不是同一个类型 std::shared_ptr<Node> _prev; std::shared_ptr<Node> _next; ~Node() { cout << "~Node()" << endl; } }; // shared_ptr的循环引用 void SharedPtrTest3() { std::shared_ptr<Node> node1(new Node); std::shared_ptr<Node> node2(new Node); node1->_next = node2; node2->_prev = node1; }
循环引用分析
node1 和 node2 两个智能指针对象指向两个节点,引用计数变成 1,我们不需要手动 delete。
node1 的 _next 指向 node2,node2 的_prev 指向node1,引用计数变成 2。
node1 和 node2 析构,引用计数减到 1,但是 _next 还指向下一个节点,_prev 还指向上一个节点。
也就是说 _next 析构了,node2 才能释放。
也就是说 _prev 析构了,node1 才能释放。
但是 _next 属于 node1 的成员,node1 释放了,_next 才会析构。而 node1 由 _prev 管理,_prev 属于node2 成员,node2 释放了,_prev 才会行。所以这就叫循环引用,谁也不会释放。
shared_ptr 的循环引用问题通过 weak_ptr 来解决。weak_ptr 并不是常规的智能指针,没有 RAII,不支持直接管理资源。weak_ptr 主要用 shared_ptr 来构造,用来解决 shared_ptr 的循环引用问题。
weak_ptr 调用拷贝构造和赋值运算符重载是不会增加资源的引用计数的,它不参与资源的管理。
struct Node { int _val; std::weak_ptr<Node> _prev; std::weak_ptr<Node> _next; ~Node() { cout << "~Node()" << endl; } }; // shared_ptr的循环引用 void SharedPtrTest4() { std::shared_ptr<Node> node1(new Node); std::shared_ptr<Node> node2(new Node); // 注:use_count函数返回的是引用计数的大小 cout << node1.use_count() << endl; cout << node2.use_count() << endl; cout << "--------" << endl; node1->_next = node2; node2->_prev = node1; cout << node1.use_count() << endl; cout << node2.use_count() << endl; cout << "--------" << endl; }
weak_ptr 的简单模拟实现
namespace Joy { // weak_ptr是辅助型智能指针,主要用于解决shared_ptr的循环引用问题 template <class T> class weak_ptr { public: weak_ptr() : _ptr(nullptr) {} weak_ptr(const shared_ptr<T>& sp) : _ptr(sp.get()) {} weak_ptr(const weak_ptr<T>& wp) : _ptr(wp._ptr) {} weak_ptr<T>& operator=(const shared_ptr<T>& sp) { _ptr = sp.get(); return *this; } weak_ptr<T>& operator=(const weak_ptr<T>& wp) { _ptr = wp._ptr; return *this; } T& operator*() { return *_ptr; } T* operator->() { return _ptr; } private: T* _ptr; }; struct Node { int _val; // 注:Node*和std::shared_ptr<Node>不是同一个类型 Joy::weak_ptr<Node> _prev; Joy::weak_ptr<Node> _next; ~Node() { cout << "~Node()" << endl; } }; // shared_ptr的循环引用 void SharedPtrTest5() { Joy::shared_ptr<Node> node1(new Node); Joy::shared_ptr<Node> node2(new Node); cout << node1.use_count() << endl; cout << node2.use_count() << endl; cout << "--------" << endl; node1->_next = node2; node2->_prev = node1; cout << node1.use_count() << endl; cout << node2.use_count() << endl; cout << "--------" << endl; } }
shared_ptr 是有线程安全问题的,需要加锁保护。这部分内容等学习完线程部分再来补充。
定制删除器
如果不是 new 出来的对象如何通过智能指针管理呢?或者是 new[ ] 出来的对象如果通过智能指针管理呢?那么就需要给 shared_ptr 设计定制删除器来解决这个问题。
new 内置类型,内置类型不会去调用构造函数。new[ ] 调用的是 operator new[ ],operator new[ ] 调用的是 malloc;delete[ ] 调用的是 oprator delete[ ],operator delete[ ] 调用的是 free。因为 delete[ ] 和 delete 都是调用 free 来释放空间,所以对于内置类型不会存在太大的问题(可能一些编译器会进行是否匹配使用的检查)。而对于自定义类型,就需要看该类型有没写析构函数。如果写了析构函数,使用 new[ ] 时,VS 会在最前面多开四个字节来存储对象的个数。当你使用 delete 来释放 new[ ] 申请的空间时,就会存在释放的位置不对且析构函数少调用了的问题。因为 VS 还给你多开了四个字节的空间,来存储对象的个数。而如果你没有写析构函数,那么编译器会自动生成析构函数。编译器自己生成析构函数的话,就不会在前面多开一个字节来存储申请空间的个数,也不会去调用析构函数,所以程序就没有报错(与内置类型不报错的原因类似)。
template<class T> struct DeleteArray { void operator()(T* ptr) { cout << "delete[]:" << ptr << endl; delete[] ptr; } }; template<class T> struct Free { void operator()(T* ptr) { cout << "free:" << ptr << endl; free(ptr); } }; // 定制删除器 void SharedPtrTest7() { std::shared_ptr<Node> n1(new Node[5], DeleteArray<Node>()); std::shared_ptr<Node> n2(new Node); std::shared_ptr<int> n3(new int[5], DeleteArray<int>()); std::shared_ptr<int> n4((int*)malloc(sizeof(int)), Free<int>()); }
注:在拷贝构造时传给 shared_ptr 的定制删除器也可以是 lambda 表达式,传给 unique_ptr 的定制删除器是定制删除器的类型。
我们的改造 unique_ptr 和 shared_ptr 都是在类模板里传定制删除器的类型。
namespace Joy { template<class T> struct Delete { void operator()(T* ptr) { //cout << "delete:" << ptr << endl; delete ptr; } }; template<class T> struct DeleteArray { void operator()(T* ptr) { cout << "delete[]:" << ptr << endl; delete[] ptr; } }; template<class T> struct Free { void operator()(T* ptr) { cout << "free:" << ptr << endl; free(ptr); } }; template<class T, class D = Delete<T>> class unique_ptr { public: unique_ptr(T* ptr = nullptr) : _ptr(ptr) {} // C++98的防拷贝做法:将拷贝构造和赋值运算符重载弄成私有,只声明不实现 // C++11的防拷贝做法:delete unique_ptr(unique_ptr<T>& up) = delete; unique_ptr<T>& operator=(unique_ptr<T>& up) = delete; ~unique_ptr() { if (_ptr) { cout << "Delete:" << _ptr << endl; D()(_ptr); } } T& operator*() { return *_ptr; } T* operator->() { return _ptr; } private: T* _ptr; }; template<class T, class D = Delete<T>> class shared_ptr { public: shared_ptr(T* ptr = nullptr) : _ptr(ptr) , _pCount(new int(1)) {} shared_ptr(const shared_ptr<T>& sp) : _ptr(sp._ptr) , _pCount(sp._pCount) { ++(*_pCount); } void Release() { // 减减对象的计数,如果是最后一个对象,要释放资源 if (--(*_pCount) == 0) { //cout << "Delete:" << _ptr << endl; D()(_ptr); delete _pCount; } } ~shared_ptr() { Release(); } shared_ptr<T>& operator=(const shared_ptr<T>& sp) { // 通过_ptr来判断管理的资源是否是同一个。如果是同一个, // _ptr相等,直接返回即可 //if(this == &sp) if (_ptr == sp._ptr) { return *this; } Release(); // 减减被赋值对象的计数,如果是最后一个对象,要释放资源 // 共管新资源,++计数 _ptr = sp._ptr; _pCount = sp._pCount; ++(*_pCount); return *this; } T& operator*() { return *_ptr; } T* operator->() { return _ptr; } int* GetCount() const { return _pCount; } T* get() const { return _ptr; } int use_count() { return *_pCount; } private: T* _ptr; int* _pCount; // 引用计数 }; void SharedPtrTest8() { Joy::shared_ptr<Node, DeleteArray<Node>> n1(new Node[5]); Joy::shared_ptr<Node> n2(new Node); Joy::shared_ptr<int, DeleteArray<int>> n3(new int[5]); Joy::shared_ptr<int, Free<int>> n4((int*)malloc(sizeof(int))); } }
智能指针重点内容
👉C++11和boost中智能指针的关系👈
C++ 98 中产生了第一个智能指针 auto_ptr。
C++ boost 给出了更实用的 scoped_ptr、shared_ptr 和 weak_ptr。
C++ TR1 引入了 shared_ptr 等。不过注意的是 TR1 并不是标准版。
C++ 11 引入了 unique_ptr、shared_ptr 和 weak_ptr。需要注意的是 unique_ptr 对应 boost 的scoped_ptr,并且这些智能指针的实现原理是参考 boost 中的实现的。
👉总结👈
本篇博客主要讲解了什么是内存泄漏、内存泄漏的危害及分类、什么是智能指针和 RAII、auto_ptr、unique_ptr、shared_ptr 和 weak_ptr 以及定制删除器等等。那么以上就是本篇博客的全部内容了,如果大家觉得有收获的话,可以点个三连支持一下!谢谢大家!💖💝❣️