为什么引入智能指针?
C++的动态内存需要用户自己维护,动态开辟空间,在出函数作用域或者程序退出时释放内存,否则会造成内存泄漏。当代码教长或者复杂时,我们会忘记释放空间。
void Fun1()
{
int*p = new int[10];
FILE* pFile = fopen("1.txt", "r");
if (pFile == NULL)
{
return; //如果打开失败,返回,造成内存泄漏
}
if (p != NULL)
{
delete[]p;
p = NULL;
}
}
void Fun2()
{
int *p = new int[10];
try
{
//dosomething();
}
catch (...)
{
return; //返回之前忘记释放内存,导致内存泄漏
}
delete[] p;
}
智能指针的基本情况
1 RAII(资源分配即初始化)
定义一个类封装资源的分配和释放,在构造函数完成资源的分配和初始化
在析构函数完成资源的清理,可用保证资源正确初始化和释放。
2 像指针一样 operator* operator->
3 赋值和拷贝
Boost库的智能指针
(ps:新的C++11标准中已经引入了unique_ptr/shared_ptr/weak_ptr)
scoped_ptr
scoped_array
shared_ptr
shared_array
weak_ptr
intrusive_ptr
模拟实现库中的部分智能指针
1 auto_ptr
思想:采用的是资源转移的方式,在拷贝和赋值后,原有对象对资源无访问权。
template<typename T>
class AutoPtr
{
public:
AutoPtr(T* ptr)
:_ptr(ptr)
{
ptr = NULL;//与动态申请的空间脱离
cout << "AutoPtr()" << endl;
}
~AutoPtr()
{
if (NULL != _ptr)
{
cout << "~AutoPtr()" << endl;
delete _ptr;
_ptr = NULL;
}
}
private:
T* _ptr;
};
void Funtest()
{
AutoPtr<int> p = new int;
AutoPtr<int>p1(p); ///p资源被转移到p1,p无法进行访问
}
int main()
{
Funtest();
system("pause");
return 0;
}
不写拷贝构造函数默认浅拷贝,同一块被释放了两次,导致程序崩溃。
改进:添加拷贝构造函数
template<typename T>
class AutoPtr
{
public:
AutoPtr(T* ptr)
:_ptr(ptr)
{
ptr = NULL;//与动态申请的空间脱离
cout << "AutoPtr()" << endl;
}
AutoPtr(AutoPtr<T>& ptr) //由于内部要改变ptr的值,所以去掉const
:_ptr(ptr._ptr)
{
ptr = NULL; //让ptr与动态申请的空间脱离关系
}
~AutoPtr()
{
if (NULL != _ptr)
{
cout << "~AutoPtr()" << endl;
delete _ptr;
_ptr = NULL;
}
}
private:
T* _ptr;
};
void Funtest()
{
AutoPtr<int> p = new int;
AutoPtr<int>p1(p); //拷贝构造
}
int main()
{
Funtest();
system("pause");
return 0;
}
智能指针管理权转移:资源被转移,原来的对象无法进行访问,所以只调用了一次析构函数
构造函数的参数要给缺省值
template<typename T>
class AutoPtr
{
public:
AutoPtr(T* ptr=NULL)
:_ptr(ptr)
{
ptr = NULL;//与动态申请的空间脱离
cout << "AutoPtr()" << endl;
}
AutoPtr(AutoPtr<T>& ptr) //由于内部要改变ptr的值,所以去掉const
:_ptr(ptr._ptr)
{
ptr = NULL; //让ptr与动态申请的空间脱离关系
}
~AutoPtr()
{
if (NULL != _ptr)
{
cout << "~AutoPtr()" << endl;
delete _ptr;
_ptr = NULL;
}
}
private:
T* _ptr;
};
void Funtest()
{
AutoPtr<int> p = new int;
AutoPtr<int>p1(p); //拷贝构造
AutoPtr<int> p2;
}
int main()
{
Funtest();
system("pause");
return 0;
}
赋值运算符重载
template<typename T>
class AutoPtr
{
public:
AutoPtr(T* ptr=NULL)
:_ptr(ptr)
{
cout << "AutoPtr()" << endl;
}
AutoPtr(AutoPtr<T>& ptr) //由于内部要改变ptr的值,所以去掉const
:_ptr(ptr._ptr)
{
ptr._ptr = NULL; //让ptr与动态申请的空间脱离关系
}
AutoPtr<T>& operator=(AutoPtr<T>& ptr)
{
if (this != &ptr)
{
if (NULL != _ptr) //防止空对象进行引用导致程序崩溃
{
delete _ptr;
_ptr = ptr._ptr; //如果_ptr为空,则崩溃
ptr._ptr = NULL;
}
}
return *this;
}
~AutoPtr()
{
if (NULL != _ptr)
{
cout << "~AutoPtr()" << endl;
delete _ptr;
_ptr = NULL;
}
}
private:
T* _ptr;
};
void Funtest()
{
AutoPtr<int> p = new int;
AutoPtr<int>p1(p); //拷贝构造
AutoPtr<int> p2;
p2 = p1; //赋值运算符重载
}
int main()
{
Funtest();
system("pause");
return 0;
}
获取原生指针 重载* 和->
template <typename T>
class AutoPtr
{
public:
AutoPtr(T* ap = NULL)
:_p(ap)
{
cout << "AutoPtr()" << endl;
}
AutoPtr(AutoPtr<T>& ap)//由于内部要改变ap的值,所以去掉const修饰
:_p(ap._p)
{
ap._p = NULL;//让ap与动态申请的空间脱离关系
}
AutoPtr<T>& operator=(AutoPtr<T>& ap)
{
if (this != &ap)
{
if (NULL != _p)//防止对空对象进行引用导致程序崩溃
{
delete _p;
_p = ap._p;//如果_p为空,则崩溃
ap._p = NULL;//脱离关系
}
}
return *this;
}
~AutoPtr()
{
if (NULL != _p)
{
cout << "~AutoPtr()" << endl;
delete _p;
_p = NULL;
}
}
T* Get()
{
return _p;
}
T* operator*()
{
return *_p;
}
T* operator->()
{
return _p;
}
private:
T* _p;
};
void Funtest()
{
AutoPtr<int> p = new int;
AutoPtr<int> p1(p);
AutoPtr<int> p2;
p2 = p1;
AutoPtr<int>* p3 = &p1;
cout << *(p3->Get)() << endl;
}
int main()
{
Funtest();
system("pause");
return 0;
}
auto_ptr
特点:带有缺陷的设计
方式:管理权转移防止一块空间被多次释放(即资源被转移,原对象无法进行访问)
scoped_ptr
思想:让单个对象独占空间,避免对象被多次释放的问题
一个类要防止被拷贝要做两件事情
1》将拷贝构造函数和赋值运算符重载函数的访问权设置为私有
2》将拷贝构造函数和赋值运算符重载函数只声明不定义
template <typename T>
class scopted_ptr
{
public:
scopted_ptr(T* ptr = NULL)
:_ptr(ptr)
{
cout << "scopted_ptr()" << endl;
}
~scopted_ptr()
{
if (NULL != _ptr)
{
cout << "~scopted_ptr()" << endl;
delete _ptr;
_ptr = NULL;
}
}
T* Get()
{
return _ptr;
}
T* operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
private:
scopted_ptr(const scopted_ptr<T>& ptr);
scopted_ptr<T>& operator=(const scopted_ptr<T>& ptr);
private:
T* _ptr;
};
void Funtest()
{
scopted_ptr<int> p1= new int;
scopted_ptr<int> *p2= &p1;
cout << *(p2->Get)() << endl;
}
int main()
{
Funtest();
system("pause");
return 0;
}
注意:避免出错,不要进行赋值和对象拷贝
引用计数版本的智能指针—->shared_ptr
之前我们学习string类时,运用了写诗拷贝的方法解决浅拷贝存在的问题,同样我们借助它的原理来实现shared_ptr
它在底层多申请了一块空间保存引用计数,用来检测当前对象所管理的空间是否还被其他智能指针使用,在构造函数中,若该智能指针所指空间非空,将其引用计数初始化为1,析构时,先对其引用计数-1,若减为0,就可释放空间,否则不能释放
template<class T>
class SharedPtr
{
public:
SharedPtr(T* ptr = NULL)
:_ptr(ptr)
, _pCount(NULL)
{
if (_ptr != NULL)
{
_pCount = new int(1);
}
}
SharedPtr(const SharedPtr<T>& sp)
:_ptr(sp._ptr)
, _pCount(sp._pCount)
{
++GetRef();
}
SharedPtr<T>& operator=(const SharedPtr<T>& sp)
{
if (this != &sp)
{
Release();
_ptr = sp._ptr;
_pCount = sp._pCount;
++GetRef();
}
return *this;
}
T* operator->()
{
return _ptr;
}
T& operator*()
{
return *_ptr;
}
int UseCount()
{
return GetRef();
}
~SharedPtr()
{
Release();
}
private:
void Release()
{
if (_ptr && 0 == --GetRef())
{
delete _ptr;
delete _pCount;
}
}
int& GetRef()
{
return *_pCount;
}
private:
T* _ptr;
int* _pCount;
};
void TestSharedPtr()
{
SharedPtr<int> sp1(new int(10));
SharedPtr<int> sp2(sp1);
*sp1 = 20;
*sp2 = 30;
cout << sp1.UseCount() << endl;//2
cout << sp2.UseCount() << endl;//2
SharedPtr<int> sp3(new int(50));
SharedPtr<int> sp4(sp3);
sp1 = sp3;
sp2 = sp4;
cout << sp1.UseCount() << endl;//4
cout << sp2.UseCount() << endl;//4
}
int main()
{
TestSharedPtr();
return 0;
}
这种方式看似完美,但是也有一个问题
试想有一个双向链表,每个节点的next和prev都是SharedPtr类型的智能指针,则此时的p1,p2引用计数都为2,但函数结束时要销毁p1,p2,p1的引用计数先减1后为1,说明还有其他指针使用这块空间,因此不能释放,P2也是如此,它的引用计数减1后也为1,无法释放,二者相互制约,最终都无法释放导致内存泄漏。