目录
一、为什么需要智能指针?
int div()
{
int a, b;
cin >> a >> b;
if (b == 0)
throw invalid_argument("除0错误");
return a / b;
}
void Func()
{
int* p1 = new int;
cout << div() << endl;
delete p1;
}
int main()
{
try
{
Func();
}
catch (exception& e)
{
cout << e.what() << endl;
}
return 0;
}
在上述场景中,Func()函数中div()一但报异常,那么就会直接跳到main函数中的catch块中,导致跳过了delete p1这句代码而不能释放p1,进而导致内存泄漏。在日常使用new/malloch等函数中,程序员经常会忘记delete/free,从而导致内存泄漏,我们需要一种东西帮我们管理内存的申请和释放,所以有了智能指针。
二、内存泄漏
2.1 什么时内存泄漏
内存泄漏指因为疏忽或错误造成程序未能释放已经不再使用的内存的情况。内存泄漏并不是指内存在物理上的消失,而是应用程序分配某段内存后,因为设计错误,失去了对该段内存的控制,因而造成了内存的浪费。
2.2 内存泄漏的危害
长期运行的程序出现内存泄漏,影响很大,如操作系统、后台服务等等,出现
内存泄漏会导致响应越来越慢,最终卡死。
2.3 内存泄漏的分类
- 堆内存泄漏(Heap leak)
堆内存指的是程序执行中依据须要分配通过malloc / calloc / realloc / new等从堆中分配的一
块内存,用完后必须通过调用相应的 free或者delete 删掉。假设程序的设计错误导致这部分
内存没有被释放,那么以后这部分空间将无法再被使用,就会产生Heap Leak。
- 系统资源泄漏
指程序使用系统分配的资源,比方套接字、文件描述符、管道等没有使用对应的函数释放
掉,导致系统资源的浪费,严重可导致系统效能减少,系统执行不稳定。
2.4 如何避免内存泄漏
1) 工程前期良好的设计规范,养成良好的编码规范,申请的内存空间记着匹配的去释放。
2) 采用RAII思想或者智能指针来管理资源。
三、智能指针
3.1 RAII
RAII(Resource Acquisition Is Initialization)是一种利用对象生命周期来控制程序资源(如内存、文件句柄、网络连接、互斥量等等)的简单技术。
在对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终保持有效,最后在对象析构的时候释放资源。借此,我们实际上把管理资源的责任托管给了一个对象。这种做法有两大好处:
- 不需要显式地释放资源。
- 采用这种方式,对象所需的资源在其生命期内始终保持有效
3.2 智能指针
智能指针就是利用了RAII的思想,通过智能指针这个对象来管理资源,同时还具备指针的特性能够使用 "*" 和 "->"操作符,下面简单实现了智能指针。
template<class T>
class SmartPtr {
public:
SmartPtr(T* ptr = nullptr)
: _ptr(ptr)
{}
~SmartPtr()
{
if(_ptr)
delete _ptr;
}
T& operator*() {return *_ptr;}
T* operator->() {return _ptr;}
private:
T* _ptr;
};
struct Date
{
int _year;
int _month;
int _day;
};
int main()
{
SmartPtr<int> sp1(new int);
*sp1 = 10
SmartPtr<int> sparray(new Date);
// 需要注意的是这里应该是sparray.operator->()->_year = 2018;
// 本来应该是sparray->->_year这里语法上为了可读性,省略了一个->
sparray->_year = 2018;
sparray->_month = 1;
sparray->_day = 1;
return 0;
}
3.3 std::auto_ptr
C++98版本的库中就提供了auto_ptr的智能指针,但是auto_ptr有一个巨大的缺陷就是当你要拷贝一份auto_ptr时,它使用了管理权转移的思想,从而导致拷贝后,被拷贝的指针就失效了。
int main()
{
std::auto_ptr<int> ap1(new int);
std::auto_ptr<int> ap2(ap1); //管理权转移,ap1失效
*ap2 = 10;
cout << *ap2 << endl;
cout << *ap1 << endl; //报错
return 0;
}
3.4 std::unique_ptr
C++11中提供unique_ptr,unique_ptr不能被拷贝。
3.5 std::shared_ptr
C++11中提供shared_ptr,shared_ptr支持拷贝,shared_ptr通过引用计数的方式来实现多个shared_ptr对象之间共享资源。具体细节如下:
1) shared_ptr在其内部,给每个资源都维护了着一份计数,用来记录该份资源被几个对象共
享,指向同一份资源的shared_ptr共用一个计数。
2) 在对象被销毁时(也就是析构函数调用),就说明自己不使用该资源了,对象的引用计数减
1。
3) 如果引用计数是0,就说明自己是最后一个使用该资源的对象,必须释放该资源;
4) 如果不是0,就说明除了自己还有其他对象在使用该份资源,不能释放该资源,否则其他对象就成野指针了。下面代码简单实现了shared_ptr,注意shared_ptr需要保证线程安全,所以在某些地方有加锁操作。
template<class T>
class shared_ptr
{
public:
shared_ptr(T* ptr = nullptr) //构造函数
:_ptr(ptr)
, _pRefCount(new int(1))
, _pmtx(new mutex)
{}
shared_ptr(const shared_ptr<T>& sp) //拷贝构造
:_ptr(sp._ptr)
, _pRefCount(sp._pRefCount)
, _pmtx(sp._pmtx)
{
AddRef();
}
void Release()
{
_pmtx->lock();
bool flag = false;
if (--(*_pRefCount) == 0 && _ptr)
{
cout << "delete:" << _ptr << endl;
delete _ptr;
delete _pRefCount;
flag = true;
}
_pmtx->unlock();
if (flag == true)
{
delete _pmtx;
}
}
void AddRef()
{
_pmtx->lock();
++(*_pRefCount);
_pmtx->unlock();
}
shared_ptr<T>& operator=(const shared_ptr<T>& sp) //赋值运算符重载
{
if (_ptr != sp._ptr)
{
Release();
_ptr = sp._ptr;
_pRefCount = sp._pRefCount;
_pmtx = sp._pmtx;
AddRef();
}
return *this;
}
int use_count()
{
return *_pRefCount;
}
~shared_ptr() //析构函数
{
Release();
}
// 像指针一样使用
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
T* get() const
{
return _ptr;
}
private:
T* _ptr;
int* _pRefCount; //这里不能使用static变量,因为static变量是所有资源共用一个计数
mutex* _pmtx;
};
文章评论