智能指针
1.智能指针的作用
手动申请的资源,如果没释放就会导致内存泄漏,为了预防内存泄露,可以通过智能指针管理资源,让智能指针对象在指针生命周期结束时主动释放资源。
2.智能指针的原理
(1)RAII特性;
(2)重载operator*和operator->,用对象模拟原生指针的行为
RALL特性
RAII是什么?
一种利用对象生命周期来控制程序资源(如内存,文件句柄,套接字,互斥量等等)的简单技术
RAII是怎样做的?
在对象构造时获取资源,控制对资源的访问使之在对象的生命周期内始终有效,最后在对象析构的时候释放资源。借此,我们实际上把管理一份资源的任务交给了一个对象。
RAII的好处:
不需要显示地释放资源;
通过采用这种方式,对象所需的资源在生命周期内始终保持有效。
RAII设计守卫锁
class LockGuard
{
public:
LockGuard(Mutex& mtx)
:_mutex(mtx)
{
_mutex.lock();
}
~LockGuard()
{
_mutex.unlock();
}
LockGuard(const LockGuard<Mutex>&) = delete;
private:
// 注意这里必须使用引用,否则锁的就不是一个互斥量对象
Mutex& _mutex;
};
通过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;
};
3.智能指针的特点
指针生命周期结束时主动释放资源;
杜绝指针运算和指针比较;
4.常见的智能指针
(1)auto_ptr
实现原理:
管理权转移,也就是当把一个auto_ptr对象ap中的内容拷贝到另一个auto_ptr对象中时,会将ap和它所管理的资源断开联系,将资源转移到当前对象中
因为auto_ptr的资源会转移,所以禁止使用该智能指针
模拟实现auto_ptr
//一个会在拷贝和赋值时资源转移的智能指针类
template<class T>
class Auto_ptr
{
public:
Auto_ptr(T* ptr)
:_ptr(ptr)
{}
Auto_ptr(const Auto_ptr& obj)
{
//资源转移
_ptr = obj._ptr;
const_cast<Auto_ptr<T>&>(obj)._ptr = nullptr;
}
Auto_ptr& operator=(const Auto_ptr& obj)
{
if (this != &obj)
{
//释放原空间
if (_ptr)
{
delete _ptr;
_ptr = nullptr;
}
//资源转移
_ptr = obj._ptr;
const_cast<Auto_ptr<T>&>(obj)._ptr = nullptr;
}
}
~Auto_ptr()
{
if (_ptr)
{
delete _ptr;
_ptr = nullptr;
}
}
T* operator->()
{
return _ptr;
}
T& operator*()
{
return *_ptr;
}
//判断资源是否存在
bool isNull()
{
return _ptr == nullptr;
}
T* getPtr()
{
return _ptr;
}
//声明为protected可以让在被继承时
//让基类的成员不能在类外被访问,只能在派生类中被访问
protected:
T* _ptr = nullptr;
};
(2)unique_ptr
实现原理:防拷贝
模拟实现
template<typename T>
class UniquePtr{
public:
UniquePtr(T* ptr)
:_ptr(ptr)
{}
~UniquePtr()
{
if (_ptr)
delete _ptr;
}
//防拷贝
UniquePtr(UniquePtr& obj) = delete;
UniquePtr& operator=(UniquePtr& obj) = delete;
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
private:
T* _ptr;
};
(3)shared_ptr
实现原理
通过引用计数的方式来实现对多个shared_ptr对象之间资源的共享
模拟实现
计数器加1和资源的释放都是非原子操作,存在线程安全问题,所以需要加锁保护。
template<class T>
class SharedPtr{
public:
SharedPtr(T* ptr = nullptr)
:_ptr(ptr)
, _count(new int(1))
, _mutex(new mutex)
{}
~SharedPtr()
{
Release();
}
SharedPtr(SharedPtr& obj)
:_ptr(obj._ptr)
, _count(obj._count)
, _mutex(obj._mutex)
{
//计数++
AddCount();
}
SharedPtr& operator=(SharedPtr& obj)
{
//if (this != &obj)
if(_ptr != obj._ptr)
{
//释放旧资源
Release();
//共享管理的资源
_ptr = obj._ptr;
_count = obj._count;
_mutex = obj._mutex;
//计数++
AddCount();
}
return *this;
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
int UseCount()
{
return *_count;
}
T* getPtr()
{
return _ptr;
}
private:
T* _ptr; //管理的资源
int* _count; //计数器
mutex* _mutex; //互斥锁
void AddCount()
{
_mutex->lock();
++(*_count);
_mutex->unlock();
}
void Release()
{
bool flag = false;//是否计数器变为0
_mutex->lock();
if (--(*_count) == 0)
{
delete _ptr;
delete _count;
_ptr = nullptr;
_count = nullptr;
flag = true;
}
_mutex->unlock();
if (flag == true)
{
delete _mutex;
_mutex = nullptr;
}
}
};
shared_ptr存在的问题:循环引用
当两个对象中有智能指针成员且相互指向时,在调用析构函数后计数-1,但是计数没有减到0,他在等待另外一个智能指针对象释放资源才释放资源,但是另一个对象也在等待该对象释放资源,双方陷入一种类似死锁的场景,可以通过使用weak_ptr解决,weak_ptr作为智能指针成员时,即使相互指向也不会增加引用计数,因此释放资源时不会受影响。
自定义资源释放方式:删除器
自定义释放空间的方式,通过定义一个仿函数类,仿函数类中指定释放空间的方式
删除器示例:
template<class T>
struct DeleteArrDel
{
void operator()(T* ptr)
{
delete[] ptr;
}
};
加入删除器的SharedPtr
template<class T>
struct DeleteDel
{
void operator()(T* ptr)
{
delete ptr;
}
};
template<class T>
struct FreeDel
{
void operator()(T* ptr)
{
free(ptr);
}
};
template<class T>
struct DeleteArrDel
{
void operator()(T* ptr)
{
delete[] ptr;
}
};
//Del:删除器(定义删除数据的格式)
template<class T,class Del=DeleteDel<T>>
class SharedPtr{
public:
SharedPtr(T* ptr = nullptr,Del del=DeleteDel<T>)
:_ptr(ptr)
, _count(new int(1))
, _mutex(new mutex)
, _del(del)
{}
~SharedPtr()
{
Release();
}
SharedPtr(SharedPtr& obj)
:_ptr(obj._ptr)
, _count(obj._count)
, _mutex(obj._mutex)
{
//计数++
AddCount();
}
SharedPtr& operator=(SharedPtr& obj)
{
//if (this != &obj)
if (_ptr != obj._ptr)
{
//释放旧资源
Release();
//共享管理的资源
_ptr = obj._ptr;
_count = obj._count;
_mutex = obj._mutex;
//计数++
AddCount();
}
return *this;
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
int UseCount()
{
return *_count;
}
T* getPtr()
{
return _ptr;
}
private:
T* _ptr; //管理的资源
int* _count; //计数器
mutex* _mutex; //互斥锁
Del _del; //删除器
void AddCount()
{
_mutex->lock();
++(*_count);
_mutex->unlock();
}
void Release()
{
bool flag = false;//是否计数器变为0
_mutex->lock();
if (--(*_count) == 0)
{
//delete _ptr;
//通过删除器释放空间
_del(_ptr);
delete _count;
_ptr = nullptr;
_count = nullptr;
flag = true;
}
_mutex->unlock();
if (flag == true)
{
delete _mutex;
_mutex = nullptr;
}
}
};
删除器使用示例:
struct A{
int _a;
A()
:_a(1024)
{}
~A()
{
cout << "~A()" << endl;
}
};
void test()
{
SharedPtr<A,DeleteArrDel<A>> ptr(new A[10],DeleteArrDel<A>());
}
测试结果:
4.C++和boost中的智能指针的关系
(1) C++ 98 中产生了第一个智能指针auto_ptr.
(2)C++ boost给出了更实用的scoped_ptr、shared_ptr和weak_ptr.
(3)C++ TR1,引入了shared_ptr等。不过注意的是TR1并不是标准版。
(4)C++ 11,引入了unique_ptr、shared_ptr和weak_ptr。需要注意的是unique_ptr对应boost的scoped_ptr。并且这些智能指针的实现原理是参考boost中的实现的。