目录
为什么需要智能指针?
什么是内存泄露?
如何避免内存泄露?
什么是RAII?
RAII有什么用?
智能指针的原理是什么?
C++的智能指针有哪些?
auto_ptr
unique_ptr
weak_ptr
为什么需要智能指针?
先看下面的代码:
如果fun()函数抛出了异常,根据上篇异常的知识,当前代码块没有捕获异常,那么异常就会到上一个函数调用栈,也就是调用test函数的代码块匹配异常,那么m1和m2指向的空间就没有人释放,于是会造成内存泄露。
什么是内存泄露?
因为疏忽或者错误,进程不能释放已经不再使用的内存的情况。
显然内存泄漏是不能容忍的,因为长期运行的进程,如果出现内存泄露,会影响很大,导致系统响应越来越慢,最终卡死,因为内存空间不断的减少。比如操作系统,后台服务。
内存泄露的种类:
堆内存的泄漏
系统资源的泄露,比如套接字,文件描述符,管道
如何避免内存泄露?
- 编码规范,简单而言就是申请的内存务必记得释放,但从上面的代码可看出,如果遇到了异常也不能解决异常导致的问题,于是需要下面的方法。
- 采用RAII思想或者智能指针来管理资源
- 使用内存管理库
- 使用内存泄露检测工具
什么是RAII?
RAII:即Resource Acquisition Is Initialization,资源获取即初始化,一种利用对象的生命周期来控制资源的技术。注意,这个思想是后面实现智能指针的原理。
RAII有什么用?
- 不需要显式释放资源
- 对象所需要的资源在他的生命周期内中保持有效
智能指针的原理是什么?
1.利用RAII特性:让对象的生命周期去控制我们资源的生命周期
2.面向对象思想,智能指针用类实现,需要重载运算符让智能指针有像指针一样的行为
C++的智能指针有哪些?
基于不同的场景,智能指针的实现和使用不同将智能指针进行分类。
(注意:实现不同的本质是管理指针的方法不同,这里主要体现在如何拷贝智能指针对象上)
auto_ptr
C++98版本提供了auto_ptr智能指针
- 它的拷贝构造函数和赋值重载函数是基于管理权转移的思想实现的
实现如下:
template <class T>//智能指针要管理不同类型的指针,实现为类模板
class auto_ptr//基于管理权转移的思想
{
typedef auto_ptr<T> self;
public:
auto_ptr(T* ptr)
:_ptr(ptr)
{}
auto_ptr(self& s)
:_ptr(s._ptr)//转移指针
{
s._ptr = nullptr;
}
self& operator=(self& s)
{
if (_ptr)delete _ptr;//直接释放原指针
_ptr = s._ptr;//转移指针
s._ptr = nullptr;
return *this;
}
~auto_ptr()
{
if (_ptr)
delete _ptr;
}
T* operator->() { return _ptr; }
T operator*() { return *_ptr; }
private:
T* _ptr;
};
C++11参考了boost库中智能指针的实现,实现了三种智能指针:
unique_ptr
unique_ptr基于防拷贝的思路,直接使用关键字delete删除拷贝构造函数和赋值重载函数
实现如下:
template <class T>//智能指针要管理不同类型的指针,实现为类模板
class unique_ptr//基于禁止拷贝的思想
{
typedef unique_ptr<T> self;
public:
unique_ptr(T* ptr = nullptr)
:_ptr(ptr)
{}
unique_ptr(const self& s) = delete;
self& operator=(const self& s) = delete;
~unique_ptr()
{
if (_ptr)
delete _ptr;
}
T* operator->() { return _ptr; }
T operator*() { return *_ptr; }
private:
T* _ptr;
};
shared_ptr
可以拷贝的智能指针,shared_ptr利用引用计数,记录指向资源的指针数目,对象析构的时候计数减一,当引用计数为0的时候才释放资源,于是可以实现如下:
template <class T>//智能指针要管理不同类型的指针,实现为类模板
class shared_ptr//利用引用计数实现允许拷贝的智能指针
{
typedef shared_ptr<T> self;
public:
shared_ptr(T* ptr = nullptr)
:_ptr(ptr)
,_pcount(new int(1))
{}
shared_ptr(self& s)
:_ptr(s._ptr)
,_pcount(s._pcount)
{
++(*_pcount);
}
self& operator=(const self& s)
{
if (_ptr != s._ptr)
{
if (--(*_pcount) == 0 && _ptr && _pcount)//当前指针计数--,s的指针计数++
{
delete _pcount;
delete _ptr;
}
_ptr = s._ptr;
_pcount = s._pcount;
++(*_pcount);
}
return *this;
}
~shared_ptr()
{
if (--(*_pcount) == 0 && _ptr && _pcount)
{
delete _ptr;
delete _pcount;
}
}
T* operator->() { return _ptr; }
T operator*() { return *_ptr; }
int use_count() { return *_pcount; }//返回引用计数
T* get() const { return _ptr; }//返回指针
private:
T* _ptr;
int* _pcount;//引用计数,当不同对象指针相同时,
//其计数的地址需要一样,计数大小才能同步,故存储在堆空间
};
因为引用计数存储在堆空间,改变引用计数的函数,有线程安全问题,为了保持线程安全,底层要有互斥锁。
于是可以实现线程安全的版本如下:
#include <mutex>
using std::mutex;
template <class T>//智能指针要管理不同类型的指针,实现为类模板
class shared_ptr//利用引用计数实现允许拷贝的智能指针
{
typedef shared_ptr<T> self;
public:
shared_ptr(T* ptr = nullptr)
:_ptr(ptr)
,_pcount(new int(1))
,_pmutex(new mutex)
{}
shared_ptr(const self& s)
:_ptr(s._ptr)
,_pcount(s._pcount)
,_pmutex(s._pmutex)
{
(*_pmutex).lock();
++(*_pcount);
(*_pmutex).unlock();
}
void Release()
{
(*_pmutex).lock();
bool flag = false;//判断是否需要释放锁,因为这里不能在临界区释放锁
if (--(*_pcount) == 0 && _ptr && _pcount)//当前指针计数--,s的指针计数++
{
delete _ptr;
delete _pcount;
flag = true;
}
(*_pmutex).unlock();
if (flag)
delete _pmutex;
}
self& operator=(const self& s)
{
if (_ptr != s._ptr)
{
Release();
_ptr = s._ptr;
_pcount = s._pcount;
_pmutex = s._pmutex;
(*_pmutex).lock();
++(*_pcount);
(*_pmutex).unlock();
}
return *this;
}
~shared_ptr()
{
Release();
}
T* operator->() { return _ptr; }
T operator*() { return *_ptr; }
int use_count() { return *_pcount; }//返回引用计数
T* get() const { return _ptr; }//返回指针
private:
T* _ptr;
int* _pcount;//引用计数,当不同对象指针相同时,
//其计数的地址需要一样,计数大小才能同步,故存储在堆空间
mutex* _pmutex;//保证线程安全
};
注意:智能指针内部实现了加锁,故是线程安全的,但是指针指向的资源不是线程安全的,需要访问的人处理。
weak_ptr
什么是循环引用问题?
比如如下代码:
struct listnode
{
shared_ptr<listnode> _prev=shared_ptr<listnode>(nullptr);
shared_ptr<listnode> _next= shared_ptr<listnode>(nullptr);
int _data = 0;
~listnode()
{
std::cout << "~listnode" << std::endl;
}
};
int main()
{
shared_ptr<listnode>n1 = new listnode;
shared_ptr<listnode>n2 = new listnode;
n2->_prev = n1;
n1->_next = n2;
return 0;
}
节点中两个指针用智能指针来实现,当节点相内部指针互指向的时候,节点的引用计数都增加到2,析构时智能指针释放,节点引用计数都减为1,所以两个节点的资源都不会被释放,会造成内存泄露,如图:
为了解决shared ptr指针循环引用的问题,引入weak_ptr,其拷贝构造函数和赋值重载函数参数是shared_ptr类型的对象,拷贝时不会增加其底层的引用计数,即不参与管理我们的资源。上述循环引用问题,只需要将节点内成员shared_ptr 改成 weak_ptr 即可解决。
实现如下:
template <class T>//智能指针要管理不同类型的指针,实现为类模板
class weak_ptr//直接拷贝shared_ptr 的指针,不增加引用计数->不参与资源的管理
{
public:
weak_ptr(T* ptr = nullptr)
:_ptr(ptr)
{}
weak_ptr(const shared_ptr<T>& s)
:_ptr(s.get())
{}
weak_ptr<T>& operator=(const shared_ptr<T>& s)
{
_ptr = s.get();
return *this;
}
~weak_ptr()
{}
T* operator->() { return _ptr; }
T operator*() { return *_ptr; }
private:
T* _ptr;
};