作者:小 琛
欢迎转载,请标明出处
智能指针的提出
C++语言诞生时间较早,本身没有gc。(gc即垃圾回收器,不需要手动释放资源空间)
因此就有了本身编写代码容易出现的问题:内存空间泄露。又或者在某段代码抛异常后,就又可能发生异常安全的问题。
RAII思想的引入
RAII(Resource Acquisition Is Initialization)是一种利用对象生命周期来控制程序资源(如内存、文件句柄、网络连接、互斥量等等)的简单技术。
在对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终保持有效,最后在对象析构的时候释放资源。
借此,我们实际上把管理一份资源的责任托管给了一个对象。这种做法有两大好处:
- 不需要显示地释放资源
- 采用这种方式,对象所需的资源在其生命期内始终保持有效
//RAII思想
namespace RAII
{
template <class T>
class SmartPtr
{
public:
SmartPtr( T* ptr = nullptr)
:_ptr(ptr)
{}
~SmartPtr()
{
delete _ptr;
_ptr = nullptr;
std::cout << "delete _ptr" << std::endl;
}
T* operator ->()
{
return _ptr;
}
T& operator *()
{
return *_ptr;
}
private:
T* _ptr;
};
}
void text()
{
RAII::SmartPtr<int> s(new int(1));
}
int main()
{
text();
return 0;
}
借助类的特性,会自动释放空间
C++引入的三大智能指针
- auto_ptr(C++98引入)
因缺陷明显,几乎已经被淘汰,不推荐使用 - unique_ptr(C++11引入)
推荐使用 - shared_ptr(C++11引入)
重点学习
auto_ptr的使用与缺陷
通过例子看简单使用与出现的问题:
#include <memory>
class Date
{
public:
Date() { cout << "Date()" << endl;}
~Date(){ cout << "~Date()" << endl;}
int _year;
int _month;
int _day;
};
int main()
{
auto_ptr<Date> ap(new Date);
auto_ptr<Date> copy(ap);
// auto_ptr的问题:当对象拷贝或者赋值后,前面的对象就悬空了
ap->_year = 2018;
return 0;
}
结果:
原因:auto_ptr对于拷贝构造时采用的是管理权转移法
当需要拷贝构造的时候,由于这里的类仅仅起到一个保管指针的作用,因此无法进行深拷贝。如果不加处理,可能导致一块空间被两个对象内的指针指向,从而在析构的时候出现一块空间被释放两次而崩溃。
- 管理权转换法
对于上述的问题,auto_ptr采用管理权转换法,即如果要拷贝构造,将原来空间的指向权交给新的拷贝对象,将原来的指针悬空。
- 新问题
管理权转换法带的新问题非常明显且严重,会直接将原来的指针悬空,像要再通过原来的指针操作就不可能了。因此在C++的发展中,它逐渐被淘汰了。
unique_ptr
C++11提出,思想简单粗暴,禁止该智能指针(类)的拷贝构造。
template <class T>
class unique_ptr
{
public:
unique_ptr(T* ptr = nullptr)
:_ptr(ptr)
{}
~unique_ptr()
{
delete _ptr;
_ptr = nullptr;
}
//暴力定义不支持拷贝构造和=
unique_ptr(unique_ptr<T>& ap) = delete;
unique_ptr<T>& operator=(unique_ptr<T>& ap) = delete;
T& operator *()
{
return _ptr;
}
T* operator ->()
{
return *_ptr;
}
private:
T* _ptr
};
更加靠谱的shared_ptr
shared_ptr的原理:是通过引用计数的方式来实现多个shared_ptr对象之间共享资源。即,通过计数的方式查看该智能指针(类)是否为最后一个指向该资源的对象,如果是进行空间释放,否则不进行。例如:最后一个离开的人再关灯锁门。
- shared_ptr在其内部,给每个资源都维护了着一份计数,用来记录该份资源被几个对象共享。
- 在对象被销毁时(也就是析构函数调用),就说明自己不使用该资源了,对象的引用计数减一。
- 如果引用计数是0,就说明自己是最后一个使用该资源的对象,必须释放该资源;
- 如果不是0,就说明除了自己还有其他对象在使用该份资源,不能释放该资源,否则其他对象就成野指针了。
这里增加了多线程的情况,考虑线程安全问题。
template <class T>
class shared_ptr
{
public:
shared_ptr(T* ptr)
:_ptr(ptr)
, *_count(1)
{}
shared_ptr(shared_ptr<T>& ap)
:_ptr(ap._ptr)
,_count(ap._count)
{
//管理资源的成员数加1
(*_count)++;
}
shared_ptr<T>& operator =(shared_ptr<T>& ap)
{
if (this != &ap)
{
//共同管理资源
_ptr = ap._ptr;
_count = ap._count;
(*_count)++;
}
return *this;
}
~shared_ptr()
{
if (--(*_count) == 0)
{
delete _ptr;
delete _count;
_ptr = nullptr;
_count = nullptr;
std::cout << "delete shared_ptr" << std::endl;
}
else
{
_ptr = nullptr;
_count = nullptr;
}
}
T& operator *()
{
return _ptr;
}
T* operator ->()
{
return *_ptr;
}
private:
T* _ptr;
int* _count;
};