本篇博文旨在介绍C++中的智能指针;从为什么引入它开始,分别实现了auto_ptr,scoped_ptr,unique_ptr,shared_ptr等智能指针;介绍了各个智能指针的特点;最后用防函数和智能指针实现了文件指针的管理
智能指针概念
智能指针是一个类,用它的对象管理着申请的内存空间,并通过作用域、生命周期来保证申请内存的释放,从而防止出现内存泄漏
内存泄漏举例
#include<iostream>
using namespace std;
//为什么需要智能指针
//每次返回都需要对指针进行处理
//很是麻烦
//所以要引出智能指针,可以自出进行内存的释放
void FunTest()
{
int* p = new int[10];
FILE* pFile = fopen("1.txt", "r");
if (pFile == NULL)//如果打开失败,那么p就成为了一个野指针
{
return;
}
if (p != NULL)
{
delete[] p;
p = NULL;
}
}
在上述代码中,如果文件打开失败,那么p指针就没有进行释放,会造成内存泄漏
如果我们每次在返回语句前进行释放,当前面new申请的空间很多时,会不会太麻烦了
智能指针
1、RAII
RAII称为“资源获取就是初始化”,是c++等编程语言常用的管理资源
避免内存泄露的方法。它保证在任何情况下,
使用对象时先构造对象,最后析构对象。
2、boost库
了解智能指针就需要了解boost库,C++11引入的智能指针标准中,参考了boost库中对智能指针的实现
模拟实现智能指针
1、autoptr——不推荐使用的智能指针
template<typename T>
class AutoPtr
{
public:
AutoPtr(T* ap = NULL)
:_p(ap)
{
ap = NULL;
cout << "AutoPtr()" << endl;
}
AutoPtr(AutoPtr& ap)
:_p(ap._p)
{
ap._p = NULL;
//ap = NULL;
}
AutoPtr& operator=(AutoPtr&ap)
{
if (this != &ap)
{
if (_p != NULL)
{
delete _p;
_p = ap._p;
ap._p = NULL;
}
}
return *this;
}
~AutoPtr()
{
if (_p != NULL)
{
cout << "~AutoPtr()" << endl;
delete[] _p;
_p = NULL;
}
}
T* Get()
{
return _p;
}
T& operator*()
{
return *_p;
}
T* operator->()
{
return _p;
}
private:
T* _p;
};
auto_ptr的缺陷:
只能由一个对象来管理空间
当多个auto_ptr指向同一块空间时,会由于多次释放而导致崩溃
2、ownerptr——设置布尔变量来表示有没有管理空间
当进行拷贝构造,进行管理权限的交换
只有掌握空间权限的才可以进行释放
template <typename T>
class OwnerPtr
{
public:
//构造函数
OwnerPtr(T* ap = NULL)
:_p(ap)
, _owner(true)
{
cout << "OwnerPtr()" << endl;
}
//拷贝构造函数
OwnerPtr(OwnerPtr<T>& ap)
:_p(ap._p)
, _owner(true)
{
ap._owner = false;
}
OwnerPtr<T>& operator=(OwnerPtr<T>& ap)
{
if (this != &ap)
{
if (_p != NULL)
{
delete _p;
_p = ap._p;
_owner = true;
ap._owner = false;
}
}
return *this;
}
~OwnerPtr()
{
if (_owner && _p != NULL)
{
cout << "~OwnerPtr()" << endl;
delete _p;
_p = NULL;
_owner = false;//false代表释放完毕
}
}
T* Get()
{
return _p;
}
T* operator*()
{
return *_p;
}
T* operator->()
{
return _p;
}
private:
T* _p;
bool _owner;//表示对象是否占用资源
};
3、scopedptr——最容易实现的智能指针
scoped_ptr直接不允许进行拷贝以及赋值运算符重载,故而没有释放两次导致崩溃的事情发生
template<typename T>
class ScopedPtr
{
public:
ScopedPtr(T* ap = NULL)
:_p(ap)
{}
~ScopedPtr()
{
if (_p != NULL)
{
delete _p;
_p = NULL;
}
}
T* Get()
{
return _p;
}
T& operator*()
{
return *_p;
}
T* operator->()
{
return _p;
}
private:
ScopedPtr(const ScopedPtr& ap);
ScopedPtr<T>& operator=(const ScopedPtr& ap);
//下面是另一种防止拷贝的方法
/*ScopedPtr(const ScopedPtr& ap) = delete;
ScopedPtr<T>& operator=(const ScopedPtr& ap) = delete;*/
protected:
T* _p;
};
4、uniqueptr——类似于一个动态开辟的数组
由于可以用vector实现,所以用的不多
并且不存在于boost库
//unique_ptr
//重载了[]
//就比如一个vecotr开辟的动态数组
template<typename T>
class UniquePtr
{
public:
UniquePtr(T* ap)
:_p(ap)
{}
T& operator[](const size_t index)
{
return _p[index];
}
const T& operator[](const size_t index)const
{
return _p[index];
}
T* Get()
{
return _p;
}
protected:
T* _p;
};
void Funtest()
{
UniquePtr<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] << " ";
}
}
5、sharedptr——引用计数版本的只能指针
通过对管理一块空间的智能指针进行计数,只当_pCount == 0 的时候再进行释放
否则就 --_pCount
template<typename T>
class SharedPtr
{
public:
SharedPtr(T* _ap)
:_p(ap)
, _pCount(NULL)
{
//当对象不为空时,对_pCount进行引用计数统计
if (_p != NULL)
{
_pCount = new int[1];
}
}
SharedPtr(SharedPtr<T> &ap)
:_p(ap._p)
, _pCount(ap._pCount)
{
//如果对象不为空
//则增加引用计数
if (p != NULL)
{
++(*_pCount);
}
sp._p = NULL;
}
SharedPtr<T> operator=(SharedPtr<T> &ap)
{
if (this != &ap)
{
//没有管理空间
if (_pCount == NULL)
{
_p = ap._p;
_pCount = ap._pCount;
if (_pCount != NULL)
++(*_pCount);
}
else if (*_pCount == 1)//独自管理一段空间
{
delete _p;
delete _pCount;
_p = ap._p;
_pCount = ap._pCount;
if (_pCount != NULL)
++(*_pCount);
}
else//和别人共享空间
{
--(*_pCount);
_p = ap._p;
_pCount = ap._pCount;
if (_pCount != NULL)
++(*_pCount);
}
}
return *this;
}
protected:
T* _p;
int* _pCount;
};
6、sharedptr的循环引用解决方法---weakptr
sharedptr的循环引用
template<typename T>
class Node
{
public:
Node(const T& value)
: _value(value)
{
cout << "Node()" << endl;
}
~Node()
{
cout << "~Node()" << endl;
cout << "this:" << this << endl;
}
shared_ptr<Node<T>> _pNext;
shared_ptr<Node<T>> _pPre;
T _value;
};
void TestSharedPtr()
{
shared_ptr<Node<int>> s1(new Node<int>(1));
shared_ptr<Node<int>> s2(new Node<int>(2));
cout << "s1-use_count:" << s1.use_count() << endl;
cout << "s2-use_count:" << s2.use_count() << endl;
s1->_pNext = s2;
s2->_pPre = s1;
cout << "s1-use_count:" << s1.use_count() << endl;
cout << "s2-use_count:" << s2.use_count() << endl;
}
我们看到,当 s1->_pNext = s2; s2->_pPre = s1; 时
他们各自的引用计数会自增
这样就会导致释放的时候 use_count -1 = 1;并不会释放
就造成了内存泄漏
利用weakptr解决循环引用问题
template<typename T>
class Node
{
public:
Node(const T& value)
: _value(value)
{
cout << "Node()" << endl;
}
~Node()
{
cout << "~Node()" << endl;
cout << "this:" << this << endl;
}
weak_ptr<Node<T>> _pNext;
weak_ptr<Node<T>> _pPre;
T _value;
};
void TestSharedPtr()
{
shared_ptr<Node<int>> s1(new Node<int>(1));
shared_ptr<Node<int>> s2(new Node<int>(2));
cout << "s1-use_count:" << s1.use_count() << endl;
cout << "s2-use_count:" << s2.use_count() << endl;
s1->_pNext = s2;
s2->_pPre = s1;
cout << "s1-use_count:" << s1.use_count() << endl;
cout << "s2-use_count:" << s2.use_count() << endl;
}
实现文件指针释放的两种该方法
1、函数指针
//利用函数实现文件指针的关闭
void FClose(FILE* pf)
{
if (pf)
fclose(pf);
}
void FunTest2()
{
FILE* p = fopen("1.txt", "r");
shared_ptr<FILE> sp(p, FClose);
}
2、利用防函数定制删除器
//利用防函数来控制文件指针的关闭
class FClose//定制删除器
{
void operator()(FILE* p)
{
if (p)
fclose(p);
}
};
void FunTest3()
{
FILE* p = fopen("1.txt", "r");
shared_ptr<FILE> sp(p, FClose);
}