一、为什么要引入智能指针?
我们知道,C++中的动态内存需要用户自己来维护,动态开辟的空间,在出函数作用域或者程序正常退出前必须释放掉,否则会造成内存泄露,虽然有时我们已经非常谨慎了,然而当代吗比较长或比较复杂时,我们仍然可能忘记释放,正所谓防不胜防,一起看一段代码:
- void FunTest1()
- {
- int *p = new int[10];
- FILE* pFile = fopen( "1.txt", "r" );
- if (pFile == NULL)
- {
- return;
- }
-
- if (p != NULL)
- {
- delete[] p;
- p = NULL;
- }
- }
- void FunTest2()
- {
- int *p = new int[10];
- try
- {
-
- }
- catch (...)
- {
- return;
- }
- delete[] p;
- }
从上述两个例子中可以看出,在动态申请空间中,每一次返回之前都必须对动态内存进行释放,否则就内存泄漏,这样做不仅麻烦,还容易忘记,那么,怎样解决上述问题呢?就不得不引入我们的新概念---->智能指针(auto_ptr)
。
二、智能指针的基本情况
1、资源分配即初始化RAII(Resource Acquisition Is Initialization):定义一个类来封装资源的分配和释放,在构造函数完成资源的分配和初始化,在析构函数完成资源的清理,可以保证资源的正确初始化和释放。
2、Boost库的智能指针
(ps:新的C++11标准中已经引入了unique_ptr/shared_ptr/weak_ptr)
三、模拟实现库中的某些智能指针
1、新库中的智能指针---->auto_ptr
(1)单纯管理空间
- template <typename T>
- class AutoPtr
- {
- public:
- AutoPtr(T* ap)
- :_p(ap)
- {
- ap = NULL;
- cout<<"AutoPtr()"<<endl;
- }
- ~AutoPtr()
- {
- cout<<"~AutoPtr()"<<endl;
- if(NULL != _p)
- {
- delete _p;
- _p = NULL;
- }
- }
- private:
- T* _p;
- };
- void Funtest()
- {
- AutoPtr<int> p = new int;
- }
- int main()
- {
- Funtest();
- system("pause");
- return 0;
- }
运行结果:
(2)拷贝对象
- template <typename T>
- class AutoPtr
- {
- public:
- AutoPtr(T* ap)
- :_p(ap)
- {
- ap = NULL;
- cout<<"AutoPtr()"<<endl;
- }
- ~AutoPtr()
- {
- if(NULL != _p)
- {
- cout<<"~AutoPtr()"<<endl;
- delete _p;
- _p = NULL;
- }
- }
- private:
- T* _p;
- };
- void Funtest()
- {
- AutoPtr<int> p = new int;
- AutoPtr<int> p1(p);
- }
- int main()
- {
- Funtest();
- system("pause");
- return 0;
- }
ERROR:同一块被释放了两次,导致程序崩溃。
改进:添加拷贝构造函数
- template <typename T>
- class AutoPtr
- {
- public:
- AutoPtr(T* ap)
- :_p(ap)
- {
- ap = NULL;
- cout<<"AutoPtr()"<<endl;
- }
- AutoPtr(AutoPtr<T>& ap)
- :_p(ap._p)
- {
- ap = NULL;
- }
- ~AutoPtr()
- {
- if(NULL != _p)
- {
- cout<<"~AutoPtr()"<<endl;
- delete _p;
- _p = NULL;
- }
- }
- private:
- T* _p;
- };
- void Funtest()
- {
- AutoPtr<int> p = new int;
- AutoPtr<int> p1(p);
- }
- int main()
- {
- Funtest();
- system("pause");
- return 0;
- }
运行结果:
(3)构造函数的参数要给缺省值
- template <typename T>
- class AutoPtr
- {
- public:
- AutoPtr(T* ap)
- :_p(ap)
- {
- ap = NULL;
- cout<<"AutoPtr()"<<endl;
- }
- AutoPtr(AutoPtr<T>& ap)
- :_p(ap._p)
- {
- ap = NULL;
- }
- ~AutoPtr()
- {
- if(NULL != _p)
- {
- cout<<"~AutoPtr()"<<endl;
- delete _p;
- _p = NULL;
- }
- }
- private:
- T* _p;
- };
- void Funtest()
- {
- AutoPtr<int> p = new int;
- AutoPtr<int> p1(p);
- AutoPtr<int> p2;
- }
- int main()
- {
- Funtest();
- system("pause");
- return 0;
- }
error:
改进:
- template <typename T>
- class AutoPtr
- {
- public:
- AutoPtr(T* ap <span style="color:#ff0000;">= NULL</span>)
- :_p(ap)
- {
- ap = NULL;
- cout<<"AutoPtr()"<<endl;
- }
- AutoPtr(AutoPtr<T>& ap)
- :_p(ap._p)
- {
- ap = NULL;
- }
- ~AutoPtr()
- {
- if(NULL != _p)
- {
- cout<<"~AutoPtr()"<<endl;
- delete _p;
- _p = NULL;
- }
- }
- private:
- T* _p;
- };
- void Funtest()
- {
- AutoPtr<int> p = new int;
- AutoPtr<int> p1(p);
- AutoPtr<int> p2;
- }
- int main()
- {
- Funtest();
- system("pause");
- return 0;
- }
(4)赋值运算符重载
- template <typename T>
- class AutoPtr
- {
- public:
- AutoPtr(T* ap = NULL)
- :_p(ap)
- {
- cout<<"AutoPtr()"<<endl;
- }
-
- AutoPtr(AutoPtr<T>& ap)
- :_p(ap._p)
- {
- ap._p = NULL;
- }
-
- AutoPtr<T>& operator=(AutoPtr<T>& ap)
- {
- if(this != &ap)
- {
- if(NULL != _p)
- {
- delete _p;
- _p = ap._p;
- ap._p = NULL;
- }
- }
- return *this;
- }
-
- ~AutoPtr()
- {
- if(NULL != _p)
- {
- cout<<"~AutoPtr()"<<endl;
- delete _p;
- _p = NULL;
- }
- }
- private:
- T* _p;
- };
- void Funtest()
- {
- AutoPtr<int> p = new int;
- AutoPtr<int> p1(p);
- AutoPtr<int> p2;
- p2 = p1;
- }
- int main()
- {
- Funtest();
- system("pause");
- return 0;
- }
运行过程截图:
运行结果:
(5)获取原生指针,重载*和->
- template <typename T>
- class AutoPtr
- {
- public:
- AutoPtr(T* ap = NULL)
- :_p(ap)
- {
- cout<<"AutoPtr()"<<endl;
- }
-
- AutoPtr(AutoPtr<T>& ap)
- :_p(ap._p)
- {
- ap._p = NULL;
- }
-
- AutoPtr<T>& operator=(AutoPtr<T>& ap)
- {
- if(this != &ap)
- {
- if(NULL != _p)
- {
- delete _p;
- _p = ap._p;
- ap._p = NULL;
- }
- }
- return *this;
- }
-
- ~AutoPtr()
- {
- if(NULL != _p)
- {
- cout<<"~AutoPtr()"<<endl;
- delete _p;
- _p = NULL;
- }
- }
- T* Get()
- {
- return _p;
- }
- T* operator*()
- {
- return *_p;
- }
- T* operator->()
- {
- return _p;
- }
- private:
- T* _p;
- };
- void Funtest()
- {
- AutoPtr<int> p = new int;
- AutoPtr<int> p1(p);
- AutoPtr<int> p2;
- p2 = p1;
- AutoPtr<int>* p3 = &p1;
- cout<<*(p3->Get)()<<endl;
- }
- int main()
- {
- Funtest();
- system("pause");
- return 0;
- }
运行过程截图:
运行结果:
auto_ptr致命缺陷:只能由一个对象管理所开辟的空间
2、老库中维护一个布尔变量owner实现---->owner_ptr
实现原理:构造对象时,将该对象的_owner赋值为TRUE,析构对象时,只有当该对象的_owner值为TRUE才释放,否则不释放。
- template <typename T>
- class AutoPtr
- {
- public:
- AutoPtr(T* ap = NULL)
- :_p(ap)
- ,_owner(true)
- {
- cout<<"AutoPtr()"<<endl;
- }
-
- AutoPtr(AutoPtr<T>& ap)
- :_p(ap._p)
- ,_owner(true)
- {
- ap._owner = false;
- }
-
- AutoPtr<T>& operator=(AutoPtr<T>& ap)
- {
- if(this != &ap)
- {
- if(NULL != _p)
- {
- delete _p;
- _p = ap._p;
- _owner = true;
- ap._owner = false;
- }
- }
- return *this;
- }
-
- ~AutoPtr()
- {
- if(_owner &&NULL != _p)
- {
- cout<<"~AutoPtr()"<<endl;
- delete _p;
- _p = NULL;
- _owner = false;
- }
- }
- T* Get()
- {
- return _p;
- }
- T* operator*()
- {
- return *_p;
- }
- T* operator->()
- {
- return _p;
- }
- private:
- T* _p;
- bool _owner;
- };
- void Funtest()
- {
- AutoPtr<int> p = new int;
- AutoPtr<int> p1(p);
- AutoPtr<int> p2;
- p2 = p1;
- AutoPtr<int>* p3 = &p1;
- cout<<*(p3->Get)()<<endl;
- }
- int main()
- {
- Funtest();
- system("pause");
- return 0;
- }
运行结果:
不足之处:
- template <typename T>
- class AutoPtr
- {
- public:
- AutoPtr(T* ap = NULL)
- :_p(ap)
- ,_owner(true)
- {
- cout<<"AutoPtr()"<<endl;
- }
-
- AutoPtr(AutoPtr<T>& ap)
- :_p(ap._p)
- ,_owner(true)
- {
- ap._owner = false;
- }
-
- AutoPtr<T>& operator=(AutoPtr<T>& ap)
- {
- if(this != &ap)
- {
- if(NULL != _p)
- {
- delete _p;
- _p = ap._p;
- _owner = true;
- ap._owner = false;
- }
- }
- return *this;
- }
-
- ~AutoPtr()
- {
- if(_owner &&NULL != _p)
- {
- cout<<"~AutoPtr()"<<endl;
- delete _p;
- _p = NULL;
- _owner = false;
- }
- }
- T* Get()
- {
- return _p;
- }
- T* operator*()
- {
- return *_p;
- }
- T* operator->()
- {
- return _p;
- }
- private:
- T* _p;
- bool _owner;
- };
- void Funtest()
- {
- AutoPtr<int> sp1;
- if(true)
- {
- AutoPtr<int> sp2(sp1);
- }<span style="color:#33ff33;">
- AutoPtr<int> sp3;
- sp3 = sp1;
- }
- int main()
- {
- Funtest();
- system("pause");
- return 0;
- }
运行过程截图:
运行结果:
总结:以上两种方式我们都是通过资源转移来防止一块空间被释放多次,auto_ptr的缺点是申请的空间最终只能由一个对象来管理,而owner_ptr虽然可通过多个对象管理空间,但却可能导致野指针等问题,这并不是我们的初衷,那么有什么更好的实现方法呢?
3、单个对象独占空间---->scoped_ptr
(1)首先思考一个问题,一个类如何防止被拷贝
- class B
- {
- public:
- B()
- {
- cout<<"B()"<<endl;
- }
- private:
- B(const B& b);
- B& operator=(const B& b);
-
- private:
- int _b;
- };
- void Funtest()
- {
- B b1;
- B b2(b1);
- b2 = b1;
- }
编译结果:
所以说,一个类要防止被拷贝,要做两件工作:
1>将拷贝构造函数和赋值运算符重载的访问权限设成私有的。
2>将拷贝构造函数和赋值运算符重载只给出声明而不对其进行定义。
(2)仿照上述思想,scopted_ptr的实现思想就是让单个对象独占空间,从而避免对象被多次释放的问题。
- template <typename T>
- class AutoPtr
- {
- public:
- AutoPtr(T* ap = NULL)
- :_p(ap)
- {
- cout<<"AutoPtr()"<<endl;
- }
-
- ~AutoPtr()
- {
- if(NULL != _p)
- {
- cout<<"~AutoPtr()"<<endl;
- delete _p;
- _p = NULL;
- }
- }
- T* Get()
- {
- return _p;
- }
- T* operator*()
- {
- return *_p;
- }
- T* operator->()
- {
- return _p;
- }
- private:
- AutoPtr(const AutoPtr<T>& ap);
- AutoPtr<T>& operator=(const AutoPtr<T>& ap);
- private:
- T* _p;
- };
- void Funtest()
- {
- AutoPtr<int> p1 = new int;
- AutoPtr<int>* p2 = &p1;
- cout<<*(p2->Get)()<<endl;
- }
- int main()
- {
- Funtest();
- system("pause");
- return 0;
- }
运行结果:
注意:应避免使用出错,不要进行对象拷贝和赋值。
4、unique_ptr(即scopted_array)
- template <typename T>
- class AutoPtr
- {
- public:
- AutoPtr(T* ap = NULL)
- :_p(ap)
- {
- cout<<"AutoPtr()"<<endl;
- }
-
- ~AutoPtr()
- {
- if(NULL != _p)
- {
- cout<<"~AutoPtr()"<<endl;
- delete _p;
- _p = NULL;
- }
- }
- T& operator[](size_t index)
- {
- return _p[index];
- }
- const T& operator[](size_t index)const
- {
- return _p[index];
- }
- T* Get()
- {
- return _p;
- }
- private:
- AutoPtr(const AutoPtr<T>& ap);
- AutoPtr<T>& operator=(const AutoPtr<T>& ap);
- private:
- T* _p;
- };
- void Funtest()
- {
- AutoPtr<int> p(new int[5]);
- p[0] = 1;
- p[1] = 2;
- p[2] = 3;
- p[3] = 4;
- p[4] = 5;
- for(size_t i=0; i<5; i++)
- {
- cout<<p[i]<<" ";
- }
- }
- int main()
- {
- Funtest();
- system("pause");
- return 0;
- }
运行结果:
注意:unique_ptr在boost库中是没有的,因为它就好比一个动态开辟的数组,可以用vector来实现,接口多,用着还方便,故有点多余。
5、引用计数版本的智能指针---->shared_ptr
前面在string类中我们介绍过引用计数版本的各种情况分析,在这里我们就不啰嗦了,直接给出指针版本的。
有兴趣了解的请看:string类详解:
http://blog.csdn.net/snow_5288/article/details/52901528
赋值运算符重载情况分类:
- template <typename T>
- class SharedPtr
- {
- public:
- SharedPtr(T* sp = NULL)
- :_p(sp)
- ,_pCount(NULL)
- {
- cout<<"SharedPtr()"<<endl;
- if(NULL != _p)
- {
- _pCount = new int(1);
- }
- }
-
- SharedPtr(SharedPtr<T>& sp)
- :_p(sp._p)
- ,_pCount(sp._pCount)
- {
- if(NULL != _p)
- ++(*_pCount);
- sp._p = NULL;
- }
-
- SharedPtr<T>& operator=(SharedPtr<T>& sp)
- {
- if(this != &sp)
- {
- if(NULL == _pCount)
- {
- _p = sp._p;
- _pCount = sp._pCount;
- if(sp._pCount)
- ++(*_pCount);
- }
- else if(_pCount && 1==(*_pCount))
- {
- delete _p;
- delete _pCount;
-
- _p = sp._p;
- _pCount = sp._pCount;
- if(sp._pCount)
- ++(*_pCount);
- }
- else
- {
- --(*_pCount);
-
- _p = sp._p;
- _pCount = sp._pCount;
- if(sp._pCount)
- ++(*_pCount);
- }
- }
- return *this;
- }
-
- ~SharedPtr()
- {
- if(_pCount && 0==(--(*_pCount)))
- {
- cout<<"~SharedPtr()"<<endl;
- delete _p;
- delete _pCount;
- _p = NULL;
- _pCount = NULL;
- }
- }
- T* operator*()
- {
- return *_p;
- }
- T* operator->()
- {
- return _p;
- }
- private:
- T* _p;
- int* _pCount;
- };
-
- void FunTest()
- {
- SharedPtr<int> sp1(NULL);
- SharedPtr<int> sp2(new int);
- SharedPtr<int> sp3(sp2);
- SharedPtr<int> sp4 = new int;
- SharedPtr<int> sp5(new int);
- sp4 = sp2;
- sp1 = sp4;
- sp2 = sp5;
- }
- int main()
- {
- FunTest();
- system("pause");
- return 0;
- }
运行过程:
运行结果:
结论:shared_ptr可通过多个对象对申请的空间进行访问。
6>定置删除器(仿函数实现)
上述5种实现智能指针的通病:由于文件指针用fopen打开之后必须拿对应的fclose函数将其关闭,故它们处理不了文件指针,所以就必须引出仿函数。
仿函数(functor):STL的六大组件之一,也叫函数对象。就是使一个类的使用看上去像一个函数。其实现原理就是就是在类中重载(),这个类就有了类似函数的行为,同时也成为一个仿函数类了。
用函数实现文件指针的关闭:
- #include<iostream>
- #include<memory>
- using namespace std;
-
- void Fclose(FILE* pf)
- {
- if(pf)
- fclose(pf);
- }
- int main()
- {
- FILE* pf = fopen("1.txt","r");
- shared_ptr<FILE> sp(pf,Fclose);
- system("pause");
- return 0;
- }
用仿函数实现:
- #include<iostream>
- #include<memory>
- using namespace std;
-
- class Fclose
- {
- public:
- void operator()(FILE* pf)
- {
- if(NULL != pf)
- fclose(pf);
- }
- };
-
- int main()
- {
- FILE* pf = fopen("1.txt","r");
- shared_ptr<FILE> sp(pf,Fclose());
- system("pause");
- return 0;
- }
7>智能指针应用举例:实现多重冒泡排序
- <pre code_snippet_id="2008345" snippet_file_name="blog_20161125_17_3228611" name="code" class="cpp">template <typename T>
- class Great
- {
- public:
- bool operator()(T& left,T& right)
- {
- return left>right;
- }
- };
- template <typename T>
- class Less
- {
- public:
- bool operator()(T& left,T& right)
- {
- return left<right;
- }
- };
- template <typename T,typename Compare>
- void BubbleSort(T arr[],size_t size)
- {
- size_t i,j;
- int flag = 0;
- for(i = 0; i<size-1; i++)
- {
- flag = 0;
- for(j = 0; j<size-1-i; j++)
- {
- if(Compare()(arr[j],arr[j+1]))
- {
- flag = 1;
- swap(arr[j],arr[j+1]);
- }
- }
- if(flag == 0)
- break;
- }
- }
-
- int main()
- {
- int arr[10] = {9,5,3,4,7,2,1,8,6,0};
- size_t size = sizeof(arr)/sizeof(arr[0]);
- BubbleSort<int,Less<int>>(arr,size);
- size_t idx = 0;
- cout<<"降序"<<endl;
- for(idx=0; idx<size; idx++)
- {
- cout<<arr[idx]<<" ";
- }
- cout<<endl<<endl;
- BubbleSort<int,Great<int>>(arr,size);
- cout<<"升序"<<endl;
- for(idx=0; idx<size; idx++)
- {
- cout<<arr[idx]<<" ";
- }
- system("pause");
- return 0;
- }</pre><br>
- <br>
- <pre></pre>
- 运行结果:
- <pre></pre>
8>智能指针<专指shared_ptr>的循环引用(非常重要)
先看一下什么是循环引用问题:
- #include <iostream>
- #include <memory>
- using namespace std;
-
- 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;
- }
运行结果:
从运行结果中我们可以清楚地看到程序运行完成之后并没有释放我们的对象sp1和sp2,那么这是什么原因造成的呢?
从上面shared_ptr的实现中我们知道了只有当引用计数减减之后等于0,析构时才会释放对象,而上述情况造成了一个僵局,那就是析构对象时先析构sp2,可是由于sp2的空间sp1还在使用中,所以sp2.use_count减减之后为1,不释放,sp1也是相同的道理,由于sp1的空间sp2还在使用中,所以sp1.use_count减减之后为1,也不释放。sp1等着sp2先释放,sp2等着sp1先释放,二者互不相让,导致最终都没能释放,内存泄漏。
解决办法:使用弱指针---->weak_ptr
- #include <iostream>
- #include <memory>
- using namespace std;
-
- template <typename T>
- struct Node
- {
- public:
- Node(const T& value)
- :_value(value)
- {
- cout<<"Node()"<<endl;
- }
- ~Node()
- {
- cout<<"~Node()"<<endl;
- cout<<"this:"<<this<<endl;
- }
-
- <span style="color:#ff0000;">weak_ptr<Node<T>> _pPre;
- weak_ptr<Node<T>> _pNext;</span>
- 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;
- }
运行结果:
分析过程:
析构对象时,先析构sp2,可是由于sp2.use_count减减之后为0,释放sp2,sp1.use_count减减之后为0,释放sp1.
小细节:由于我们的引用计数也是智能指针,维护了一块空间,当第一次调用完析构函数时,sp2.weak_count--==1未能释放,此时sp1.weak_count也减减等于1,下一次调用析构函数时,sp1.weak_count--==0释放。
boost/shared_ptr的框架uml类图: