我本次将从以下几点来分析智能指针
1.为什么要有智能指针以及智能指针的发展历史
2.智能指针的代码思想
———————————————————————————————————————————————————
1.为什么要有智能指针以及智能指针的发展历史
如果指针指向的空间是在栈上面开辟的空间,那么我们对其基本不需要干预,
因为申请和释放都是由系统根据栈的管理自己来完成的。
然而,如果指针指向的是堆上的空间。
那么该空间的new,delete都需要我们自己来完成,其空间生命周期在new和delete之间。
既然我们要自己管理这块空间,我们就要考虑内存泄漏和重复释放的问题。
这两个问题即使写程序时再仔细,也会有难以发现预测的情况。
比如有时候程序未必会执行到我们释放的那一步,说不定在中间部分就进行了异常的跳转又或者是在判断是否释放的条件上出错。
既然我们在申请和释放中要考虑这么多事,就诞生出了智能指针的问题。
智能指针用到了RAII的思想,也正是我们想要的。
RAII:资源分配即初始化,定义一个类来封装资源的分配和释放,在构造函数完成资源
的分配和初始化,在析构函数完成资源的清理,可以保证资源的正确初始化和释
放。
类似于下面这个类
#include <iostream>
using namespace std;
template <class T>
class smart_ptr
{
public:
smart_ptr(T* ptr)
:_ptr(ptr)
{}
//...
~smart_ptr()
{
delete _ptr;
}
private:
T* _ptr;
}
int main()
{
smart_ptr<int> p(new int(1)); //
}
当然,为了使p更像一个指针,下面我们还会对该类做一些优化。
发展历史
____________________________________________________________________________________________
2.智能指针的代码思想
<1>auto_ptr
我们已经用 RAII的思想解决了内存泄漏的问题,那么重复释放呢?
如果多个智能指针都指向同一块空间,析构的时候就会重复释放,出现异常,
所以auto_ptr解决的主要问题就是重复释放。
利用的方法:转移管理权限,让最后一个指向该空间的指针来管理释放。
代码如下:
template <class T>
class auto_ptr
{
public:
auto_ptr(T* ptr)
:_ptr(ptr)
{}
auto_ptr(auto_ptr<T>& ap) //拷贝构造
{
_ptr = ap._ptr;
ap._ptr = NULL; //转移管理权,ap已经不再指向该空间
}
auto_ptr<T>& operator=(auto_ptr<T>& ap) //赋值运算符的重载
{
if(_ptr != ap._ptr)
{
if(_ptr)
{
}
_ptr = ap._ptr;
ap._ptr = NULL;
}
}
//...
~auto_ptr<T>()
{
if(_ptr) //判断是否有权限释放。
{
delete _ptr;
}
}
T* operator->()
{
return _ptr; //直接返回指针,则应该是(ap->)->,但是为了增强可读性,系统只保留了一个->.
}
T& operator*()
{
return *_ptr;
}
private:
T* _ptr;
};
auto_ptr利用管理权限的转移来确保同一块空间不被释放多次,但是这样的方式当使用拷贝构造
或者赋值重载时,之前的指针就为NULL,无法调用。
所以在auto_ptr的基础上scoped_ptr干脆不让调用拷贝构造和 赋值运算符的重载。。也省去了一系列的麻烦
所以,scoped_ptr 将这两个函数声明放在private里。不让外部调用。
<2>scoped_ptr
代码如下
template <class T>
class scoped_ptr
{
public:
scoped_ptr(T* ptr)
:_ptr(ptr)
{}
//...
~scoped_ptr<T>()
{
if(_ptr) //判断是否有权限释放。
{
delete _ptr;
}
}
T* operator->()
{
return _ptr; //直接返回指针,则应该是(ap->)->,但是为了增强可读性,系统只保留了一个->.
}
T& operator*()
{
return *_ptr;
}
private:
scoped_ptr(scoped_ptr<T>& ap);
scoped_ptr& operator=(scoped_ptr& ap);
protected:
T* _ptr;
};
但是我们的意图还是需要对其使用拷贝功能,所以shared_ptr中加入了引用计数的方法,来管理
释放空间
<3>shared_ptr
template <class T>
class shared_ptr
{
public:
shared_ptr(T* ptr)
:_ptr(ptr)
,_count(new int(1))
{}
//...
~shared_ptr<T>()
{
if(--(*_count) == 0) //判断是否有权限释放。
{
delete _ptr;
}
}
T* operator->()
{
return _ptr; //直接返回指针,则应该是(ap->)->,但是为了增强可读性,系统只保留了一个->.
}
T& operator*()
{
return *_ptr;
}
shared_ptr(shared_ptr<T>& ap)
{
_ptr = ap._ptr;
_count = ap._count;
(*_count)++;
}
shared_ptr& operator=(shared_ptr& ap)
{
if(_ptr != ap._ptr)
{
this->~shared_ptr(); //释放自己所指向的空间
_ptr = ap._ptr;
_count = ap._count;
(*count)++;
}
}
protected:
T* _ptr;
int* _count;
};
但是使用shared_ptr引用计数管理释放的时,如果--count一直!=0,则该空间一直得不到释放。
例如在双向链表中使用shared_ptr
此时sp1->next->count == 2, sp2->count == 2。 如果释放sp2的空间时,--count为1,无法释放。
通过sp1->next 释放也无法释放,因为他--count 也是1.
所以,此时pre和next要使用weak_ptr。
<4>weak_ptr是专门用来针对shared_ptr的问题,比如上面的循环引用问题。
weak_ptr拷贝时,不会造成计数count的增加,所以释放时不会再互相依赖了。
代码如下
template <class T>
class WeakPtr
{
public:
WeakPtr()
:_ptr(NULL)
{}
WeakPtr(const shared_ptr<T>& sp) //专门用于管理shared_ptr
:_ptr(sp.GetPtr())
{}
WeakPtr& operator=(const shared_ptr<T> sp)
{
swap(_ptr,sp._ptr);
}
~WeakPtr()
{
if (_ptr)
{
delete _ptr;
_ptr = NULL;
}
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
private:
T* _ptr;
};
利用仿函数定制删除器
仿函数:就是使一个类的使用看上去象一个函数。其实现就是类中实现一个operator(),这个类就有了类似函数的行为,就是一个仿函数类了。
当我们进行空间资源处理的时候,一种是文件,一种是内存空间。文件对应的是fopen和fclose,内存空间对应的是
new/delete ,new[]/delete[],而智能指针的思想就是自动的完成资源的初始化和释放。
所以,利用仿函数的特性,我们可以将其加入智能指针中,帮助我们完成对不同类型指针的初始化和释放。
代码如下
#include <iostream>
#include <string>
using namespace std;
struct Fclose
{
void operator()(void *f)
{
fclose((FILE*) f);
}
};
template <class T>
struct Delete
{
void operator()(T* ptr)
{
delete ptr;
}
};
template <class T>
struct DeleteArr
{
void operator()(T* ptr)
{
delete[] ptr;
}
};
template <class T>
struct Free
{
void operator()(T* ptr)
{
free(ptr);
}
};
template <class T, class Del = Delete<T>>
class shared_ptr
{
public:
shared_ptr(T* ptr)
:_ptr(ptr)
,_count(new int(1))
,_del(Del()) //初始化一个模板类的匿名对象
{}
//...
~shared_ptr<T,Del>()
{
if(--(*_count) == 0) //判断是否有权限释放。
{
_del(_ptr); //利用仿函数释放空间
delete _count;
}
}
T* operator->()
{
return _ptr; //直接返回指针,则应该是(ap->)->,但是为了增强可读性,系统只保留了一个->.
}
T& operator*()
{
return *_ptr;
}
shared_ptr(shared_ptr<T,Del>& ap)
{
_ptr = ap._ptr;
_count = ap._count;
(*_count)++;
}
shared_ptr& operator=(shared_ptr<T,Del>& ap)
{
if(_ptr != ap._ptr)
{
this->~shared_ptr(); //释放自己所指向的空间
_ptr = ap._ptr;
_count = ap._count;
(*count)++;
}
}
protected:
T* _ptr;
int* _count;
Del _del;
};
int main()
{
shared_ptr<FILE,Fclose> p (fopen("test.txt","w"));
shared_ptr<int> i (new int(1)); //模板缺省了Delete的删除器
return 0;
}