什么是智能指针呢,它是行为类似于指针的类对象,但这种对象还有其他功能。我们为什么要封装智能指针类对象呢?这是因为C++中的动态内存需要用户自己来维护,动态开辟的空间,在出函数作用域或者程序正常退出前必须释放掉,否则会造成内存泄漏,所以我们会定义一个类来封装资源的分配和释放,在构造函数完成资源的分配和初始化,在析构函数完成资源的清理,可以保证资源的正确初始化和释放。
我们可以来看下面一段程序,这里一段空间需要释放多次,程序会十分繁琐。并且若没有及时释放很容易出现资源泄露等问题。
#include<iostream>
using namespace std;
void FunTest1()
{
throw 1;
}
bool FunTest2()
{
return false;
}
void FunTest()
{
int* p = new int[10];
FILE* fp = fopen("1.txt", "rb");
if(NULL == fp)
{
delete[] p;
p = NULL;
return;
}
try
{
FunTest1();
}
catch(...)
{
delete[] p;
p = NULL;
fclose(fp);
throw;
}
if(!FunTest2())
{
delete[] p;
p = NULL;
fclose(fp);
return;
}
delete[] p;
p = NULL;
fclose(fp);
}
int main()
{
FunTest();
return 0;
}
首先我们来模拟实现C++标准库中的AutoPtr。
#include<iostream>
using namespace std;
//第一版本AutoPtr
template<typename T>
class AutoPtr
{
public:
AutoPtr(T* ap = 0)
:_ptr(ap)
{}
AutoPtr(AutoPtr<T>& ap)//拷贝构造不能用const,因为后续要改变ap._ptr的值
:_ptr(ap._ptr)//将ap._ptr的管理权交给类中_ptr
{
ap._ptr == NULL;//置空ap
}
AutoPtr<T>& operator=(AutoPtr<T>& ap)//赋值运算符重载同样不能用const修饰
{
if(this != &ap)
{
if(_ptr)
delete _ptr;
_ptr = ap._ptr;
ap._ptr = NULL;
}
return *this;
}
~AutoPtr()
{
if(_ptr)
delete _ptr;
_ptr = NULL;
}
//基本操作
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
private:
T* _ptr;
};
int main()
{
AutoPtr<int> ap1(new int(3));
AutoPtr<int> ap2(ap1);//ap1将资源管理权限交给ap2
*ap2 = 1;
*ap1 = 2;//ap1已经将管理权限交出,此时对ap1进行解引用会出错
system("pause");
return 0;
}
接下来来实现第二版本AutoPtr。
#include<iostream>
using namespace std;
//第二版本AutoPtr
template<typename T>
class AutoPtr
{
public:
AutoPtr(T* ap)
:_ptr(ap)
,_owner(true)//若资源由自己管理则_owner为true
{
if(_ptr==NULL)
_owner = false;
}
AutoPtr(const AutoPtr<T>& ap)//可以由const修饰,因为指针的指向并不改变,只是将其_owner置为false
{
_ptr = ap._ptr;
ap._owner = false;
}
AutoPtr<T>& operator=(const AutoPtr<T>& ap)//同样有const修饰
{
if(this != &ap)
{
delete _ptr;
_ptr = ap._ptr;
_owner = true;
ap._ptr = NULL;
ap._owner = false;
}
return *this;
}
~AutoPtr()
{
if(_owner)//对这块资源有管理权限才可以释放
delete _ptr;
}
//对指针的基本操作
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
private:
T* _ptr;
mutable bool _owner;//可变的
};
int main()
{
AutoPtr<int> ap1(new int);
AutoPtr<int> ap2(ap1);
*ap1 = 1;
*ap2 = 2;
system("pause");
return 0;
}
上面这两个AutoPtr虽然第二个比第一个好些,但是指针都指向同一段空间,如果在函数结束释放空间则后续的访问也会出错。
所以又提出了ScopedPtr,这个智能指针采取比较暴力的手段,让空间只由自己一个来管理。
我们可以看一下它是如何实现的。
//防拷贝
#include<iostream>
using namespace std;
template<class T>
class ScopedPtr
{
public:
ScopedPtr(T* sp)
:_ptr(sp)
{}
~ScopedPtr()
{
if(_ptr)
delete _ptr;
_ptr = NULL;
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
private:
ScopedPtr(ScopedPtr<T>& sp);//此处拷贝构造函数与赋值运算符重载都要设置成私有声明
ScopedPtr<T>& operator=(ScopedPtr<T>& sp);
T* _ptr;
};
在这里就需要介绍一下如何防拷贝,防拷贝有三种方法:
1.声明成公有,这样的话类外的人可以给出定义。
2.声明成私有,但是把定义也给出,这种情况下友元函数可以调用,类内成员函数也可能调用。所以也不予采用。
3.声明成私有,这种方法简洁并且也能起到防拷贝的作用
接着我们继续模拟实现ScopedArray:
template<typename T>
class ScopedArray
{
public:
ScopedArray(T* s)
:_ptr(s)
{}
~ScopedArray()
{
if(_ptr)
delete[] _ptr;
}
T& operator[](int index)
{
return _ptr[index];
}
private:
ScopedArray(const ScopedArray<T>& psa);
ScopedArray<T>& operator=(ScopedArray<T> psa);
T* _ptr;
};
但是库里面并没有引进ScopedArray这是因为它类似于我们库中的Vector数组,都是以顺序方式存储数据。
最后我们来模拟boost库中shared_ptr,但这个函数线程不是很安全,一般建议使用scoped_ptr
#include<iostream>
using namespace std;
template<typename T>
class SharedPtr
{
public:
SharedPtr(T* sp = 0)
:_ptr(sp)
,_count(new int(1))
{}
SharedPtr(const SharedPtr<T>& sp)
:_ptr(sp._ptr)
,_count(sp._count)
{
++(*_count);
}
SharedPtr<T>& operator=(const SharedPtr<T>& sp)
{
if(this != &sp)
{
if(0 == --(*_count) && _ptr)
{
delete _ptr;
_ptr = NULL;
delete _count;
_count = NULL;
}
_ptr = sp._ptr;
_count = sp._count;
++(*_count);
}
return *this;
}
~SharedPtr()
{
if(_ptr && 0 == --(*_count))
{
delete _ptr;
_ptr = NULL;
delete _count;
_count = NULL;
}
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
private:
T* _ptr;
int* _count;//开辟引用计数空间
};
int main()
{
SharedPtr<int> sp1(new int(1));
SharedPtr<int> sp2(sp1);
SharedPtr<int> sp3;
sp3 = sp2;
*sp1 = 2;
*sp2 = 3;
*sp3 = 4;
system("pause");
return 0;
}
//定制删除器
#include<boost/shared_ptr.hpp>
using namespace boost;
class FClose
{
public:
void operator()(void* fp)
{
fclose((FILE*)fp);
}
};
class Free
{
public:
void operator()(void* p)
{
free(p);
}
};
void FunTest()
{
shared_ptr<FILE> p1(fopen("test.txt","r"),FClose());
shared_ptr<int> p2((int*)malloc(sizeof(int)),Free());
}
int main()
{
FunTest();
system("pause");
return 0;
}
后面还有比较重要的循环引用等问题将在下一篇博客介绍。