智能指针
1、简介
- 由于在C++中没有自动内存回收机制,所以每次程序员用new申请出来的空间都要靠自己来手动delete释放掉,这时便造成了一个麻烦,程序员很容易忘记delete申请的空间,所以此时造成了内存泄漏。这时我们便使用智能指针来解决这个问题。
本文主要对auto_ptr、scoped_ptr、share_ptr三个智能指针进行说明。
2、指针内容
2.1auto_ptr
- auto_ptr一直存在于STL中,虽然auto_ptr能够方便的管理单个堆的内存对象,但是他是一个坑,所以不建议使用。
auto_ptr到现在一般经历了三个版本,第一个版本是资源的转移,第二个版本是引用一个计数,第三个版本是回退到第一个版本。
下面模拟实现基本功能:
2.1.1auto_ptr第一种实现方法
采用资源转移,即是用一个指针将一块空间的权限全部交给了另外一个指针(权限转移)。这时便出现了一个问题。
代码如下:
#include <iostream>
using namespace std;
template<class T>
class Auto_ptr
{
public:
Auto_ptr(T* ptr=0)//构造函数
:_ptr(ptr)
{
ptr = NULL;
}
Auto_ptr(Auto_ptr<T>& ap)//拷贝构造函数
:_ptr(ap._ptr)
{
ap._ptr = NULL;
}
Auto_ptr<T>& operator = (Auto_ptr<T>& ap)//赋值运算符重载
{
if (this != &ap)
{
if (_ptr)
{
delete _ptr;
}
_ptr = ap._ptr;
ap._ptr = NULL;
}
return *this;
}
~Auto_ptr()//析构函数
{
if (_ptr)
{
delete _ptr;
_ptr = NULL;
}
}
T& operator*()
{
return *ptr;
}
T* operator->()
{
return ptr;
}
private:
T* _ptr;
};
void Funtest()
{
Auto_ptr<int> ap(new int(1));
Auto_ptr<int> ap1(ap);//ap没有与原来空间断联系
Auto_ptr<int> ap2;
ap2 = ap1;//ap2成为未绑定的auto_ptr
}
int main()
{
Funtest();
return 0;
}
上面代码运行后:
我们在ap2 = ap1的语句中ap2成为空,下面我们来分析下为什么会出现这样的问题。
问题主要是以下几个方面:
参数资源被转移
拷贝或者赋值的目标对象将先释放原来所拥有的对象。
因为一个Auto_ptr被拷贝或赋值后,已经失去对原对象的所有权,这时我们想修改第一次构造的对象时候,就会发现对象的资源已经被清理,这个时候对这个Auto_ptr的操作是不安全的。由一个常量对象去拷贝构造一个Auto_ptr
不能由一个无名对象(无名对象具有常性)去拷贝构造一个对象Auto_ptr对象,所以不能用具有常性的对象去拷贝构造一个Auto_ptr对象。
由于拷贝构造函数没有加const,赋值运算符重载也没有加const,但是在vs2010的环境下直接忽略掉,优化了,但是我们在Linux、vs2015下尝试却发现不行。
下面给出测试代码:
Auto_ptr<int> Funtest1()
{
Auto_ptr<int> ap(new int);
return ap;
}//值的形式返回返回,临时对象有常性
int main()
{
Auto_ptr<int> ap(Funtest1());//linux下再看
Auto_ptr<int> ap1(Auto_ptr<int>(new int));
//创建无名对象new上了一个int,也具有常性,理论上不能通过编译,vs2010下没有调用拷贝构造函数
//用临时对象拷贝ap1只有一种可能把临时对象直接给ap1
return 0;
}
Linux下测试代码:
2.1.2Auto_ptr第二种实现方法
第二种实现方法添加了一个引用标记_owns,如果管理空间则_owns变为true,如果没有管理空间则_owns变为false,但由于并没有让指针与原来空间断离关系,所以同样会发生内存泄漏,产生野指针的问题。
代码如下:
#include <iostream>
using namespace std;
template<class T>
class Auto_ptr
{
public:
//构造函数
explicit Auto_ptr(T* ptr = 0)
:_ptr(ptr)
, _owns(true)//只有管理了资源它才赋值上true
{
if (NULL == p)
_owns = false;
}
//拷贝构造函数,与第一个版本不一样的是加了const
Auto_ptr(const Auto_ptr<T>& ap)
:_ptr(ap._ptr)
, _owns(ap._owns)
{
ap._owns = false;//原本的没有管理所以false
}
Auto_ptr<T>& operator = (Auto_ptr<T>& ap)//赋值运算符重载
{
if (this != &ap)
{
if (_owns)
{
delete _ptr;
}
_owns = ap._owns;
_ptr = ap._ptr;
ap._owns = false;
}
return *this;
}
~Auto_ptr()//析构函数
{
if (_owns)
{
delete _ptr;
_ptr = NULL;
}
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
private:
T* _ptr;
mutable bool _owns;//mutable是可变的,如果定义一个const对象指针不能改_owns能改
};
void TestAutoPtr()
{
Auto_ptr<int> ap4(Auto_ptr<int>(new int(20)));
if (true)
{
Auto_ptr<int> ap5(ap4);
}
//ap5是true,ap4还指向原来空间
//作用域结束之后ap5已被析构,空间已被释放
*ap4 = 10;
//这时ap5与ap4共享一块空间,既然ap5已被释放,那么ap4对象维护的指针已成为野指针
}
int main()
{
TestAutoPtr();
return 0;
}
如上代码会在*ap4 = 10时出错,此时ap4已经成为野指针。
2.1.3auto_ptr第三种实现方法
第三种实现方法也是标准库里面的实现方法,运用的是类型转换由于第三种方法其实还是第一种方法,所以还是有缺陷(访问空指针的问题无法避免),我们只能避免使用。
实现代码如下:
//刚开始就给个类
template<class T>
struct Auto_ptrRef
{
Auto_ptrRef(T* ptr)
:_ptr(ptr)
{}
//没有给析构函数
T* _ptr;
};
template<class T>
class Auto_ptr
{
public:
//构造函数
Auto_ptr(T* ptr = NULL)
:_ptr(ptr)
{
cout << "Auto_ptr()" << endl;
}
//拷贝构造函数,此处为非const
Auto_ptr(Auto_ptr<T>& ap)
:_ptr(ap._ptr)
{
cout << "Auto_ptr(Auto_ptr&)" << endl;
ap._ptr = NULL;
}
Auto_ptr<T>& operator = (Auto_ptr<T>& ap)//赋值运算符重载
{
if (this != &ap)
{
if (_ptr)
{
delete _ptr;
}
_ptr = ap._ptr;
ap._owns = NULL;
}
return *this;
}
~Auto_ptr()//析构函数
{
if (_ptr)
{
delete _ptr;
_ptr = NULL;
}
}
//为了使智能指针可以像一般指针一样使用,所以重载以下函数
T* Get()
{
return _ptr;
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
//与第一第二种不同的是多了俩个类型转换
//相当于类型转换
operator Auto_ptr<T>()
{
return *this;
}
//也是类型转换转换成顶层的类
template<class T1>
operator Auto_ptrRef<T1>()
{
Auto_ptrRef<T1> temp(_ptr);
_ptr = NULL;
return temp;
}
private:
T* _ptr;
};
测试代码如下:
#include <iostream>
using namespace std;
Auto_ptr<int> FunTest()
{
Auto_ptr<int> ap1(new int(20));
return ap1;//返回值为一个匿名对象
}
Auto_ptr<int> Funtest1()
{
Auto_ptr<int> ap(new int);
Auto_ptr<int> ap1(ap);
return ap;
}
int main()
{
Auto_ptr<int> ap1(Auto_ptr<int>(new int));
FunTest();
Auto_ptr<int> ap(Funtest1());//用Linux再检验
return 0;
}
实现结果:
总结:
auto_ptr一共有以下三个阶段,但似乎是使用起来到处都是坑,所以尽量不要使用。
最后需要注意:不要误用auto_ptr
1)auto_ptr不能共享所有权,即不要让两个auto_ptr指向同一个对象。
2)auto_ptr不能指向数组,因为auto_ptr在析构的时候只是调用delete,而数组应该要调用delete[]。
3)auto_ptr只是一种简单的智能指针,如有特殊需求,需要使用其他智能指针,比如share_ptr。
4)auto_ptr不能作为容器对象,STL容器中的元素经常要支持拷贝,赋值等操作,在这过程中auto_ptr会传递所有权,那么source与sink元素之间就不等价了。
2.2Scoped_ptr
Scoped_ptr在标准库内又称为unique_ptr,它也是boost库里面所有,可以方便来管理单个堆内存对象,它与auto_ptr不一样的是不能拷贝不能赋值,叫做独享所有权。
ScopedPtr写法粗暴同时禁止转移并且独占资源(尽量不要去调用拷贝构造和赋值运算符)所以采用防拷贝。
有以下三个方法:
- 1、public上写出拷贝构造函数和赋值运算符重载函数声明,但是不定义(但是用户写出定义所以不好)。
- 2、拷贝构造函数和赋值运算符重载函数定义给成私有的,(无法防止友元)。
- 3、拷贝构造函数和赋值运算符重载函数私有声明但是不给定义。(也无法防止友元)。
下面给出第三种方法的代码:
template<typename T>
class ScopedPtr //防拷贝,防赋值,使得scopedptr看起来更像一个指针类型,但实际上scopedptr是一个类模板
{
public:
explicit ScopedPtr(T* ptr);
T& operator*();
T* operator->();
~ScopedPtr();
T* Get() const;
void Reset(T *p = 0);
void Swap(ScopedPtr<T>& sp);
protected:
ScopedPtr(const ScopedPtr<T>& sp); //将拷贝和赋值运算符声明为保护,防赋值、防拷贝
ScopedPtr<T>& operator=(const ScopedPtr<T>& ap);
private:
T* _ptr;
};
template<typename T>
T* ScopedPtr<T>::Get() const
{
return _ptr;
}
template<typename T>
void ScopedPtr<T>::Reset(T *p = 0)
{
delete _ptr;
_ptr = p;
p = NULL;
}
template<typename T>
void ScopedPtr<T>::Swap(ScopedPtr<T>& sp)
{
swap(_ptr, sp._ptr);
}
template<typename T>
ScopedPtr<T>::ScopedPtr(T* ptr)
:_ptr(ptr)
{}
template<typename T>
T& ScopedPtr<T>::operator*()
{
return *_ptr;
}
template<typename T>
T* ScopedPtr<T>::operator->()
{
return _ptr;
}
template<typename T>
ScopedPtr<T>::~ScopedPtr()
{
if (NULL != _ptr)
{
delete _ptr;
_ptr = NULL;
}
}
2.3Share_ptr
shared_ptr允许拷贝和赋值,其底层实现是以”引用计数”为基础的,通过引用计数来控制空间的释放,当一块空间创建时引用计数为1,当有新的指针指向这块空间时,引用计数加1,反之减1,直到引用计数减为0时才真的释放这块空间。所以说shared_ptr更像一个指针。
- Share_Ptr有以下几个问题
- 1、定制删除器
- 2、写出的代码不是线程安全的
- 3、循环引用(经常考)用双向链表来看
下面给出代码:
template<typename T>
//采用引用计数,实现一个可以有多个指针指向同一块内存的类模板
//SharedPtr是类模板,不是智能指针类型
class SharedPtr
{
public:
SharedPtr(T* ptr);
SharedPtr(const SharedPtr<T>& sp);
SharedPtr<T>& operator=(SharedPtr<T> sp);
T& operator*();
T* operator->();
~SharedPtr();
int Count()
{
return *_pCount;
}
private:
void Release()
{
if (--(*_pCount) == 0)
{
delete _ptr;
delete _pCount;
_ptr = NULL;
_pCount = NULL;
}
}
private:
T* _ptr;
int* _pCount; //指向引用计数的空间
};
template<typename T>
SharedPtr<T>::SharedPtr(T* ptr)
:_ptr(ptr)
, _pCount(new int(1)) {}
template<typename T>
SharedPtr<T>::SharedPtr(const SharedPtr<T>& sp)
{
_ptr = sp._ptr;
_pCount = sp._pCount;
++(*_pCount);
}
template<typename T>
SharedPtr<T>& SharedPtr<T>::operator=(SharedPtr<T> sp)
{
std::swap(sp._ptr, _ptr);
std::swap(sp._pCount, _pCount);
return *this;
}
template<typename T>
T& SharedPtr<T>::operator*()
{
return *_ptr;
}
template<typename T>
T* SharedPtr<T>::operator->()
{
return _ptr;
}
template<typename T>
SharedPtr<T>::~SharedPtr()
{
Release();
}