一、简介
为什么会有智能指针这个东西,智能指针又是什么东西。
由于 C++ 语言没有自动内存回收机制,程序员每次 new 出来的内存都要手动 delete。程序员忘记 delete,流程太复杂,最终导致没有 delete,异常导致程序过早退出,没有执行 delete 的情况并不罕见。那么就有了智能指针这个东西。
智能指针就是基于RAII的要求使用模板类,自动化地管理动态资源的释放(不管理资源的创建),智能指针看上去是指针,实际上是赋予了定义的对象。智能指针(smart pointer)是存储指向动态分配(堆)对象指针的类,用于生存期控制,能够确保自动正确的销毁动态分配的对象,防止内存泄露。
RAII(Resource Acquisition Is Initialization)是一种利用对象生命周期来控制程序资源(如内存、文件句柄、网络连接、互斥量等等)的简单技术。
二、智能指针
C++STL提供了4种智能指针,包括auto_ptr、unique_ptr(scoped_ptr)、shared_ptr和weak_ptr。
(1)auto_ptr
独占所有权,所有权转移
auto_ptr版本的智能指针采用的是管理权转移的方法,即由a构造出b对象之后,a对象置为NULL,即之后不能再访问a对象,只有b一个对象维护该空间。
模板auto_ptr是C++98提供的解决方案,C+11已将将其摒弃。,不推荐使用。
旧版本的auto_ptr,用bool值控制
旧库使用拥有者会导致野指针。
template<class T>
class AutoPtr
{
public:
AutoPtr(T *p = NULL)
:_Ptr(p)
, _owner(false)
{
if (_Ptr != NULL)
{
_owner = true;
}
}
~AutoPtr()
{
if (_owner)
{
delete _Ptr;
_Ptr = NULL;
}
}
AutoPtr(AutoPtr<T>& ap)
:_Ptr(ap._Ptr)
, _owner(ap._owner)
{
ap._owner = false;
}
AutoPtr& operator=(AutoPtr<T>& ap)
{
if (_Ptr != ap._Ptr) // 这里不要用this和ap 判断是否为自我赋值,
{
if (_owner)
delete _Ptr;
_Ptr = ap._Ptr;
_owner = ap._owner; //_owner = ture;
ap._owner = false;
}
return *this;
}
T& operator*()
{
return *_Ptr;
}
T* operator->()
{
if (_owner)
{
return _Ptr;
}
return NULL;
}
T* Get()
{
if (_owner)
{
return _Ptr;
}
return NULL;
}
private:
T* _Ptr;
bool _owner;
};
新版本的auto_ptr
template<typename T>
class AutoPtrNew
{
public:
AutoPtrNew(T *p = NULL)
:_Ptr(p)
{
}
~AutoPtrNew()
{
if (NULL != _Ptr)
{
delete _Ptr;
_Ptr = NULL;
}
}
AutoPtrNew(AutoPtrNew<T>& ap)
:_Ptr(ap._Ptr)
{
ap._Ptr = NULL;
}
AutoPtrNew& operator=(AutoPtrNew<T>& ap)
// 思路 :判断是不是自我赋值;然后判断左值是不是空,不空的话释左值;
// 右值赋值给左值,右值置空
{
if (_Ptr != ap._Ptr) // 这里不要用this和ap 判断是否为自我赋值,
{
if (NULL != _Ptr)
{
delete _Ptr;
}
_Ptr = ap._Ptr;
ap._Ptr = NULL;
}
return *this;
}
T& operator*()
{
return *_Ptr;
}
T* operator->()
{
return _Ptr;
}
T* Get()
{
return _Ptr;
}
private:
T* _Ptr;
};
(2) scoped_ptr(unique_ptr)
独占所有权,防拷贝
scoped_ptr的实现原理是防止对象间的拷贝和赋值,即将拷贝构造和赋值运算符重载放入保护或私有的访问限定符内,只声明不定义防止他人在类外拷贝。简单粗暴地解决了auto_ptr的缺点,提高了代码的安全性,但是导致功能不完整。
unique_ptr 是 C++11 标识, ScopedPtr 是 boost库提供 还有ScopedAarray。
template<class T>
class ScopedPtr
{
public:
ScopedPtr(T * p = NULL)
:_Ptr(p)
{
}
~ScopedPtr()
{
if (NULL != _Ptr)
{
delete _Ptr;
_Ptr = NULL;
}
}
T& operator*()
{
return *_Ptr;
}
T* operator->()
{
return _Ptr;
}
private:
ScopedPtr(ScopedPtr<T>& sp);//只声明不实现,并且声明为私有,防止其他人实现并调用
ScopedPtr<T>& operator=(ScopedPtr<T>& sp);
private:
T* _Ptr;
};
(3)shared_ptr
shared_ptr的实现原理是通过引用计数(int *count)来实现,拷贝或赋值时将引用计数加1,析构时只有当引用计数减到0才释放空间,否则只需将引用计数减1即可。
template<class T>
class SharedPtr
{
public:
SharedPtr(T* p = NULL)
:_Ptr(p)
, _pCount(NULL)
{
if (NULL != _Ptr)
{
_pCount = new int(1);
}
}
~SharedPtr()
//要判断计数器指针是否为空
{
if (NULL != _pCount && --(*_pCount) == 0)
{
delete _Ptr;
_Ptr = NULL;
delete _pCount;
_pCount = NULL;
}
}
SharedPtr(SharedPtr<T>& sp)
:_Ptr(sp._Ptr)
, _pCount(sp._pCount)
{
if (NULL != _pCount)
++(*_pCount);
}
SharedPtr<T>& operator=(SharedPtr<T>& sp)
//思路:判断自我赋值 左值_Ptr分三种情况 空 1个使用 多个使用
{
if (_Ptr != sp._Ptr)
{
if (NULL == _Ptr) //空
{
_Ptr = sp._Ptr;
_pCount = sp._pCount;
}
else
{
if (--(*_pCount) == 0) //1
{
delete _Ptr;
delete _pCount;
}
_Ptr = sp._Ptr;
_pCount = sp._pCount;
}
if (NULL != _pCount)
(*_pCount)++;
}
return *this;
}
int UserCount()
{
return *_pCount;
}
T* operator->()
{
return _Ptr;
}
T& operator*()
{
return *_Ptr;
}
private:
T* _Ptr;
int* _pCount;
};
循环引用的问题。
就是指两个对象进行相互引用,造成相互制约的关系,导致智能指针不能释放的问题。
测试代码
//循环引用
#include <boost/shared_ptr.hpp>
#include <boost/weak_ptr.hpp>
using namespace boost;
struct ListNode
{
shared_ptr<ListNode > _prev;
shared_ptr<ListNode > _next;
//weak_ptr<ListNode > _prev;
//weak_ptr<ListNode > _next;
~ListNode()
{
cout << "~ListNode()" << endl;
}
};
void Test()
{
// 循环引用问题
shared_ptr <ListNode > p1(new ListNode());
shared_ptr <ListNode > p2(new ListNode());
cout << "p1->Count:" << p1.use_count() << endl;
cout << "p2->Count:" << p2.use_count() << endl;
// p1节点的_next指向 p2节点
p1->_next = p2;
// p2节点的_prev指向 p1节点
p2->_prev = p1;
cout << "p1->Count:" << p1.use_count() << endl; //2
cout << "p2->Count:" << p2.use_count() << endl; //2
}
通过上面的代码结合图可以知道,p1指针的count =2,(一个是自己本身对象,另一个是p2中的prev指向p1对象),同样的p2指针的count = 2;
这样当函数运行完结束的时候,p2先释放,count = 1,没有被释放,p1再释放,count = 1,没有被释放;而且,p1对象的释放是由p2的prev指向p1的,p1的释放,需要等待p2的释放,才能完成p1的释放,同理p2的释放也需要p1的释放,才能完成自己的p2的释放。
这样的话,它们互相等待,悲剧就发生了,两个都无法释放,
循环引用–使用一个弱引用智能指针(weak_ptr)来打破循环引用(weak_ptr不增加引用计数)
weak_ptr是一个弱引用,只引用,不计数。如果一块内存被shared_ptr和weak_ptr同时引用,当所有shared_ptr析构了之后,不管还有没有weak_ptr引用该内存,内存也会被释放。所以weak_ptr不保证它指向的内存一定是有效的,在使用之前需要检查weak_ptr是否为空指针。
weak_ptr是一种不控制所指向对象生存期的智能指针,它指向一个shared_ptr管理的对象。将一个weak_ptr绑定到shared_ptr不会改变shared_ptr的引用计数。一旦最后一个指向对象的shared_ptr被销毁,对象就会被释放,即使有weak_ptr指向对象,对象还是会被释放。当我们创建一个weak_ptr时,要用一个shared_ptr来初始化它。
解决循环引用。
template<typename T>
struct ListNode{
T _value;
weak_ptr<ListNode> _prev;
weak_ptr<ListNode> _next;
ListNode(const T & value)
:_value(value)
,_prev(NULL)
,_next(NULL){}
~ListNode(){
std::cout<<"~ListNode()"<<std::endl;
}
};
void TestWeekPtr(){
std::shared_ptr<ListNode<int>> sp1(new ListNode<int>(10));
std::shared_ptr<ListNode<int>> sp2(new ListNode<int>(20));
sp1->_next = sp2;
sp2->_prev = sp1;
std::cout<<sp1.use_count()<<std::endl;
std::cout<<sp2.use_count()<<std::endl;
}
定制删除器
由于每种指针释放资源的方式不同,比如数组用delete[]释放资源,文件用fclose关闭等。我们需要针对不同的资源选择合适的释放方式,定制删除器便是通过仿函数来完成该功能。
#include<memory>
#include<iostream>
using namespace std;
class Fclose1
{
public:
void operator () (FILE *& fp)
{
if (NULL != fp)
{
fclose(fp);
fp = NULL;
}
}
};
void Fclose(FILE* fp)
{
if (NULL != fp)
{
fclose(fp);
fp = NULL;
}
}
void FunTest()
{
FILE* fp = fopen("1.txt", "w");
shared_ptr<FILE> sp1(fp, Fclose1());//仿函数
shared_ptr<FILE> sp2(fp, Fclose);//函数指针
}