C++ 智能指针(及循环引用问题)

xiaoxiao2021-02-28  146

何所谓智能指针? 所谓的智能指针就是智能/自动化的管理指指针所指向的动态资源的释放。 智能指针的产生是由于C++中没有内存的自动回收机制,每次new出来的空间都需要delete,而个别情况下,总是无法及时的delete,或者异常导致程序提早退出,造成内存泄漏,故而产生智能指针。

智能指针的发展可分为三个阶段 (1)auto_ptr c++98 (2)scoped_ptr/shared_ptr/weak_ptr boost库 (3)unique_ptr/shared_ptr/weak_ptr c++11

一、auto_ptr auto_ptr实际上是管理权限的转移,此设计本身带有缺陷,建议什么情况下都不要使用。

#include<iostream> using namespace std; template <typename T> class Auto_ptr { public: Auto_ptr(T* ptr = 0) :_ptr(ptr) {} Auto_ptr(Auto_ptr<T>& p) :_ptr(p._ptr) { p._ptr = NULL; } ~Auto_ptr() { delete _ptr; } Auto_ptr<T>&operator=(Auto_ptr<T>&p) { if (_ptr != p._ptr) { if (_ptr) { delete _ptr; } _ptr = p._ptr; p._ptr = NULL; } } T& operator*() { return *_ptr; } T& operator->() { return _ptr; } private: T* _ptr; }; int main() { Auto_ptr<int> p1(new int(10)); cout << *p1 << endl; Auto_ptr<int> p2(p1); //p1拷贝构造出p2 cout << *p1 << endl; //再次访问p1的空间 system("pause"); return 0; }

auto_ptr的核心思想就是管理权限的转移,通过赋值运算符的重载或拷贝构造时,把p1置空,即将p1开辟的空间的管理权限转交给p2后,我们进行简单操作时并不会有问题:

但管理权限转交给p2后,我们再去访问旧指针时,就会出错,这并不是我们所期待的。

二、scoped_ptr scoped_ptr又叫 守卫指针,用来防止拷贝,可以解决Auto_ptr的问题。 解决的方法: (1)拷贝构造和赋值运算符的操作可以只声明,不定义。 (2)将声明为私有,防止定义。

template<class T> class Scoped_ptr { public: Scoped_ptr(T*ptr) :_ptr(ptr) {} ~Scoped_ptr() { if (_ptr) { delete _ptr; } } T& operator*() { return *_ptr; } T* operator->() { return _ptr; } private: Scoped_ptr(const Scoped_ptr<T>& ptr); Scoped_ptr<T>& operator=(Scoped_ptr& ptr); private: T* _ptr; }; int main() { Scoped_ptr<int>p1(new int(1)); Scoped_ptr<int>p2(p1); return 0; }

指针功能明显受到限制,后来人们就不用了;于是更合理的智能指针诞生了。

三、shared_ptr 当auto_ptr和scoped_ptr都不能满足我们实际需求时,这是我们引入shared_ptr,它的主要思想是引入了引用计数,使得多个指针可以指向同一块空间,当最后一个指针释放时才真正释放这块空间。

template<class T> class Shared_ptr { public: Shared_ptr(T* ptr) :_ptr(ptr) ,_count(new int(1)) {} ~Shared_ptr() { Release(); } Shared_ptr(Shared_ptr<T>& p) :_ptr(p._ptr) , _count(p._count) { (*_count)++; } Shared_ptr<T>operator=(Shared_ptr<T>& p) { if (_ptr != p._ptr) { Release(); //先释放*this中的指针指向的空间 _ptr(p._ptr); _count(p._count); (*_count)++; } return *this; } T& operator*() { return *_ptr; } T* operator->() { return _ptr; } void Release() { if (--(*_count) == 0) { delete _ptr; delete _count; _ptr = NULL; _count = NULL; } } int GetCount() { return *_count; } T* GetPtr()const //获取指针 { return _ptr; } private: T* _ptr; int* _count; }; int main() { Shared_ptr<int> p1(new int(1)); Shared_ptr<int> p2(p1); *p1 = 10; cout << *p1 << endl; cout << p1.GetCount() << endl; cout << p2.GetCount() << endl; system("pause"); return 0; }

上面的代码虽然简单的实现引用计数版的简化版智能指针Shared_ptr;但是存在着以下问题: (1)引用计数更新存在着线程安全。 (2)循环引用的问题。 (3)定制删除器。

四、循环引用: 首先我们来举一个循环引用的例子:

template <typename T> class Node { public: Node(const T& value) :_pPre(NULL) , _pNext(NULL) , _value(value) { cout << "Node()" << endl; } ~Node() { cout << "~Node()" << endl; cout << "this:" << this << endl; } shared_ptr<Node<T>> _pPre; shared_ptr<Node<T>> _pNext; T _value; }; void Funtest() { shared_ptr<Node<int>> sp1(new Node<int>(1)); shared_ptr<Node<int>> sp2(new Node<int>(2)); cout << "sp1.use_count:" << sp1.use_count() << endl; cout << "sp2.use_count:" << sp2.use_count() << endl; sp1->_pNext = sp2; sp2->_pPre = sp1; cout << "sp1.use_count:" << sp1.use_count() << endl; cout << "sp2.use_count:" << sp2.use_count() << endl; } int main() { Funtest(); system("pause"); return 0; }

我们可以看出,并没有调用析构函数,也就是没有对空间进行释放。 从上面shared_ptr的实现中我们知道了只有当引用计数减减之后等于0,析构时才会释放对象,而上述情况造成了一个僵局,那就是析构对象时先析构sp2,可是由于sp2的空间sp1还在使用中,所以sp2.use_count减减之后为1,不释放,sp1也是相同的道理,由于sp1的空间sp2还在使用中,所以sp1.use_count减减之后为1,也不释放。sp1等着sp2先释放,sp2等着sp1先释放,二者互不相让,导致最终都没能释放,内存泄漏。

解决方案:使用弱指针—->weak_ptr(不增加引用计数)

template <typename T> struct Node { public: Node(const T& value) :_value(value) { cout << "Node()" << endl; } ~Node() { cout << "~Node()" << endl; } weak_ptr<Node<T>> _pPre; weak_ptr<Node<T>> _pNext; T _value; }; void Funtest() { shared_ptr<Node<int>> sp1(new Node<int>(1)); shared_ptr<Node<int>> sp2(new Node<int>(2)); cout << "sp1.use_count:" << sp1.use_count() << endl; cout << "sp2.use_count:" << sp2.use_count() << endl; sp1->_pNext = sp2; sp2->_pPre = sp1; cout << "sp1.use_count:" << sp1.use_count() << endl; cout << "sp2.use_count:" << sp2.use_count() << endl; } int main() { Funtest(); system("pause"); return 0; }

至于weak_ptr具体是如何工作的我们可以点击链接,看大神的解释: Boost智能指针——weak_ptr

转载请注明原文地址: https://www.6miu.com/read-42630.html

最新回复(0)