代码中途退出,也能保证资源的合理释放,在c++中没有垃圾回收机制的情况下,智能指针就可以保证我们申请的资源,最后忘记释放的问题,防止内存泄露,也帮我们减少了一定的负担,不用再在所有可能退出的地方都进行是否释放的检测。
RAII(Resource Acquisition Is Initialization)是一种利用对象生命周期来控制程序资源(如内存、文件句 柄、网络连接、互斥量等等)的简单技术
在对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终保持有效,最后在对象析构的 时候释放资源。借此,我们实际上把管理一份资源的责任托管给了一个对象
利用RAII的思想创建一个smartptr类替我们管理资源
template<class T>
class smartptr
{
public:
smartptr( T* ptr = nullptr)
:_ptr(ptr)
{
}
~smartptr()
{
if (_ptr)
{
delete _ptr;
}
}
private:
T *_ptr;
};
void TestSmartPtr()
{
smartptr<int>sp(new int);
throw 1;
}
int main()
{
try{
TestSmartPtr();
}
catch (int err)
{
cout << err << endl;
}
system("pause");
return 0;
}
这个类虽然能利用自身的生命周期帮助我们管理资源的释放,但它本身还不能称作为一个智能指针,因为它并不能解引用,所以,我们要重载解引用运算符*
和箭头运算符->
T& operator*() //重载解引用运算符
{
return *_ptr; //返回当前内容
}
T* operator->()
{
return &(operator*()); //把指针所指向空间的地址返回
}
struct A
{
int a;
int b;
int c;
};
void TestSmartPtr()
{
smartptr<int>sp1(new int);
*sp1 = 100;
cout << *sp1 << endl;
smartptr<A>sp2(new A);//我们自己的这个类,也可以用指针的方式访问数据
sp2->a = 10;
sp2->b = 20;
sp2->c = 30;
throw 1;
}
这基本就是一个简单的智能指针,但是这个“指针”有一个巨大的缺陷,它并没有处理浅拷贝问题
smartptr<A>sp3(sp2);
sp3和sp2他们管理的资源都是一样的,但是我们这里并不能用深拷贝的方式来解决这个问题,因为资源是外部的用户提供的,本身写的类并没有申请空间的权力
RAII(资源可以自动释放)+operator*()/operator->()(当成指针使用)+解决浅拷贝
RAII(资源可以自动释放)+operator*()/operator->()(当成指针使用)+资源管理权限的转移
namespace bite //自己写一个命名空间,为了防止和系统冲突
{
//auto_ptr 利用资源管理权限的转移来解决浅拷贝
template<class T>
class auto_ptr
{
//RALL
public:
auto_ptr(T *_ptr = nullptr)
:_ptr(_ptr)
{
}
//资源的转移
auto_ptr(auto_ptr<T>& ap) //要修改内容,所以不用加const
:_ptr(ap._ptr)
{
ap._ptr = nullptr;
}
~auto_ptr()
{
if (_ptr)
{
delete _ptr;
}
}
//像指针一样的方式使用
T& operator*()
{
return *_ptr;
}
T* operator->()
{
//将指针所指向空间的地址返回就可以
return &(operator*());
}
protected:
T *_ptr;
};
}
void TestAuto_ptr()
{
bite::auto_ptr<int>ap1(new int);
bite::auto_ptr<int>ap2(ap1);
}
此时我们可以看到,ap1和ap2已经不再指向同一块内存空间了,但是赋值操作也是一个浅拷贝,所以我们还要重载赋值运算符
auto_ptr<T>& operator=(auto_ptr<T>& ap)
{
if (this != &ap){ //是不是自己给自己赋值
if (_ptr) //当前对象有没有管理资源
delete _ptr; //如果有,释放
_ptr = ap._ptr; //当前对象接受ap的资源
ap._ptr = nullptr; //ap 指空
}
return *this;
}
因为auto_ptr用的是资源的转移,所以不能同时对两个对象进行操作,就像下面这样
//改进:资源管理权转移
namespace bite //自己写一个命名空间,为了防止和系统冲突
{
//auto_ptr 利用资源管理权限的转移来解决浅拷贝
template<class T>
class auto_ptr
{
//RAII
public:
auto_ptr(T *_ptr = nullptr)
:_ptr(_ptr)
, _owner(false)
{
if (_ptr)
delete _ptr;
}
//资源的转移
auto_ptr(auto_ptr<T>& ap) //要修改内容,所以不用加const
:_ptr(ap._ptr)
, _owner(ap._owner)
{
ap._owner = false;
}
auto_ptr<T>& operator=(auto_ptr<T>& ap)
{
if (this != &ap){ //是不是自己给自己赋值
if (_ptr && _owner) //当前对象有没有管理资源的权限
delete _ptr; //如果有,释放
_ptr = ap._ptr; //当前对象接受ap的资源
_owner = ap._owner;
ap._owner = false;
}
return *this;
}
~auto_ptr()
{
if (_ptr && _owner)
{
delete _ptr;
_owner = false;
}
}
//像指针一样的方式使用
T& operator*()
{
return *_ptr;
}
T* operator->()
{
//将指针所指向空间的地址返回就可以
return &(operator*());
}
protected:
T *_ptr;
bool _owner; //资源真正的管理权限
};
}
void TestAuto_ptr()
{
bite::auto_ptr<int>ap1(new int);
bite::auto_ptr<int>ap2(ap1);
ap1 = ap2;
}
int main()
{
TestAuto_ptr();
system("pause");
return 0;
}
但是这个改进后又有一个更大的缺陷???,就是造成野指针,所以还是尽量不要用这个智能指针
一个对象独占资源
unique_ptr(const unique_ptr<T>&) = delete;
unique_ptr<T>& operator=(const unique_ptr<T>&) = delete;
namespace bite
{
template<class T>
class unique_ptr
{
public:
unique_ptr(T *ptr = nullptr)
:_ptr(ptr)
{
}
~unique_ptr()
{
if (_ptr) //如果提供资源
delete _ptr; //释放掉资源
}
//具有指针的行为
T &operator*()
{
return *_ptr;
}
T *operator->()
{
return _ptr;
}
///c++98:拷贝构造函数以及赋值运算符重载只声明不定义,访问权限设置为private
//如果定义,如果用户把函数当成这个类的友员函数的话,私有权限就不复存在
/*private:
unique_ptr(const unique_ptr&);
unique_ptr& operator=(const unique_ptr&)*/
//c++11:将默认成员函数删除掉
//delete:用来释放new申请出来的空间
// 禁止编译器生成默认的成员函数
unique_ptr(const unique_ptr<T>&) = delete;
unique_ptr<T>& operator=(const unique_ptr<T>&) = delete;
private:
T * _ptr;
};
}
共享资源
RALL+operator()/operator->()+引用计数*
shared_ptr的原理:是通过引用计数的方式来实现多个shared_ptr对象之间共享资源。
namespace bite
{
template<class T>
class shared_ptr
{
public:
shared_ptr(T *ptr = nullptr)
:_ptr(ptr)
, _pCount(nullptr)
{
if (_ptr){
_pCount = new int(1);
}
}
~shared_ptr()
{
if (_ptr && 0 == --*_pCount)
{
delete _ptr;
delete _pCount;
}
}
T & operator*()
{
return *_ptr;
}
T * operator->()
{
return _ptr;
}
//采用引用计数的方式解决浅拷贝的问题
shared_ptr(const shared_ptr<T>&sp)
:_ptr(sp._ptr)
, _pCount(sp._pCount)
{
++ *_pCount;
}
//sp2 = sp1
//sp2 自己是否有资源---->没有----->直接共享
// 有 1.资源的计数是1 2.计数>1(多个资源共享)
shared_ptr<T>& operator=(const shared_ptr<T>&sp)
{
if (this != &sp) //如果不是自己给自己赋值
{
//将this从资源中分离开
if (_ptr && 0 == --*_pCount)
{
delete _ptr;
delete _pCount;
}
//共享资源
_ptr = sp._ptr;
_pCount = sp._pCount;
++*_pCount;
}
return *this;
}
private:
T *_ptr;
int *_pCount;
};
}
void TestSharedPtr()
{
bite::shared_ptr<int >sp1(new int);
bite::shared_ptr<int>sp2(sp1);
//当前对象没有资源
bite::shared_ptr<int>sp3;
sp3 = sp2;
//当前对象独立拥有资源
bite::shared_ptr<int>sp4(new int);
sp4 = sp3;
bite::shared_ptr<int>sp5(new int);
bite::shared_ptr<int>sp6(sp5);
sp5 = sp4;
}
可以看到他们共用一份资源,计数也正常,sp6用的是sp5的资源,计数变成1了。释放的时候从后往前释放
只是我们现在实现的智能指针有一些缺陷
定制删除器
//new出来资源的释放
template<class T>
struct DFDel
{
void operator()(T*& ptr)
{
if (ptr){
delete ptr;
ptr = nullptr;
}
}
};
//malooc出来的资源的释放
template<class T>
struct Free
{
void operator()(T*& ptr)
{
if (ptr){
free(ptr);
ptr = nullptr;
}
}
};
//处理文件指针的释放
struct Fclose
{
void operator()(FILE *&pf)
{
if (pf)
{
fclose(pf);
pf = nullptr;
}
}
};
namespace bite
{
template < class T, class DF = DFDel<T>>
class shared_ptr
{
public:
shared_ptr(T *ptr = nullptr)
:_ptr(ptr)
, _pCount(nullptr)
{
if (_ptr){
_pCount = new int(1);
}
}
~shared_ptr()
{
Release();
}
T & operator*()
{
return *_ptr;
}
T * operator->()
{
return _ptr;
}
//采用引用计数的方式解决浅拷贝的问题
shared_ptr(const shared_ptr<T>&sp)
:_ptr(sp._ptr)
, _pCount(sp._pCount)
{
++ *_pCount;
}
//sp2 = sp1
//sp2 自己是否有资源---->没有----->直接共享
// 有 1.资源的计数是1 2.计数>1(多个资源共享)
shared_ptr<T>& operator=(const shared_ptr<T>&sp)
{
if (this != &sp) //如果不是自己给自己赋值
{
//将this从资源中分离开
if (_ptr && 0 == --*_pCount)
{
Release();
}
//共享资源
_ptr = sp._ptr;
_pCount = sp._pCount;
++*_pCount;
}
return *this;
}
private:
void Release()
{
if (_ptr && 0 == --*_pCount)
{
//delete _ptr;
DF()(_ptr); //无名对象
delete _pCount;
}
}
private:
T *_ptr;
int *_pCount;
};
}
void TestSharedPtr()
{
bite::shared_ptr<int>sp1(new int);
bite::shared_ptr<int,Free<int>>sp2((int *)malloc(sizeof(int)));
bite::shared_ptr<FILE,Fclose>sp3(fopen(("List.h"), "r"));
}
int main()
{
TestSharedPtr();
return 0;
}
++
或--
,这 个操作不是原子的,引用计数原来是1,++了两次,可能还是2.这样引用计数就错乱了。会导致资源未 释放或者程序崩溃的问题。所以只能指针中引用计数++、–是需要加锁的,也就是说引用计数的操作是 线程安全的。我们需要加锁解决
#include
namespace bite
{
template < class T, class DF = DFDel<T>>
class shared_ptr
{
public:
shared_ptr(T *ptr = nullptr)
:_ptr(ptr)
, _pCount(nullptr)
, _pMutex(nullptr)
{
if (_ptr){
_pMutex = new mutex;
_pCount = new int(1);
}
}
~shared_ptr()
{
Release();
}
T & operator*()
{
return *_ptr;
}
T * operator->()
{
return _ptr;
}
//采用引用计数的方式解决浅拷贝的问题
shared_ptr(const shared_ptr<T>&sp)
:_ptr(sp._ptr)
,_pCount(sp._pCount)
, _pMutex(sp._pMutex)
{
AddRef();
}
//sp2 = sp1
//sp2 自己是否有资源---->没有----->直接共享
// 有 1.资源的计数是1 2.计数>1(多个资源共享)
shared_ptr<T>& operator=(const shared_ptr<T>&sp)
{
if (this != &sp) //如果不是自己给自己赋值
{
//将this从资源中分离开
if (_ptr && 0 == SubRef())
{
Release();
}
//共享资源
_ptr = sp._ptr;
_pCount = sp._pCount;
AddRef();
}
return *this;
}
size_t use_count()const
{
return *_pCount;
}
T *get()
{
return _ptr; //返回地址
}
private:
void Release()
{
if (_ptr && 0 == SubRef())
{
//delete _ptr;
DF()(_ptr); //无名对象
delete _pCount;
if (_pMutex)
delete _pMutex;
}
}
void AddRef() //引用计数变更时需要进行加锁和解锁
{
_pMutex->lock();
++*_pCount;
_pMutex->unlock();
}
int SubRef()
{
_pMutex->lock();
--*_pCount;
_pMutex->unlock();
return *_pCount;
}
private:
T *_ptr;
int *_pCount;
mutex* _pMutex;
};
}
加的锁可以保证shared_ptr是安全的,但无法保证shared_ptr所指向的内容是不是安全的,如果想要保证,必须用户自己来进行保证
我们来看这个代码
struct ListNode
{
public:
ListNode(int data)
:_data(data)
, _pPre(nullptr)
, _pNext(nullptr)
{
cout << "ListNode(int)" << this << endl;
}
~ListNode()
{
cout << "~ListNode(int):" << this << endl;
}
int _data;
shared_ptr<ListNode>_pPre;
shared_ptr<ListNode>_pNext;
//ListNode * _pPre;
//ListNode * _pNext;
};
void TestSharedPtr()
{
shared_ptr<ListNode>sp1(new ListNode(10));
shared_ptr<ListNode>sp2(new ListNode(20));
//查看引用计数
cout << sp1.use_count() << endl;
cout << sp2.use_count() << endl;
sp1->_pNext = sp2;
sp2->_pPre = sp1;
//查看引用计数
cout << sp1.use_count() << endl;
cout << sp2.use_count() << endl;
}
标准库引入了新的智能指针weak_ptr:RAII+operator*()/operator->()+引用计数,原理跟shared_ptr一样
_prev
和_next
改成weak_ptr
就可以了node1->_next = node2;
和node2->_prev = node1;
时weak_ptr
的_next
和_prev
不会增加 node1
和node2
的引用计数。struct ListNode
{
public:
ListNode(int data)
:_data(data)
//, _pPre(nullptr)
//, _pNext(nullptr)
{
cout << "ListNode(int)" << this << endl;
}
~ListNode()
{
cout << "~ListNode(int):" << this << endl;
}
int _data;
weak_ptr<ListNode>_pPre;
weak_ptr<ListNode>_pNext;
//ListNode * _pPre;
//ListNode * _pNext;
};
void TestSharedPtr()
{
shared_ptr<ListNode>sp1(new ListNode(10));
shared_ptr<ListNode>sp2(new ListNode(20));
//查看引用计数
cout << sp1.use_count() << endl;
cout << sp2.use_count() << endl;
sp1->_pNext = sp2;
sp2->_pPre = sp1;
//查看引用计数
cout << sp1.use_count() << endl;
cout << sp2.use_count() << endl;
}
int main()
{
TestSharedPtr();
return 0;
}
RAII思想除了可以用来设计智能指针,还可以用来设计守卫锁,防止异常安全导致的死锁问题
#include
#include
// C++11的库中也有一个lock_guard,下面的LockGuard造轮子其实就是为了学习他的原理 template
class LockGuard
{
public:
LockGuard(Mutex& mtx)
:_mutex(mtx)
{
_mutex.lock();
}
~LockGuard()
{
_mutex.unlock();
}
LockGuard(const LockGuard<Mutex>&) = delete;
private:
// 注意这里必须使用引用,否则锁的就不是一个互斥量对象
Mutex& _mutex;
};
mutex mtx;
static int n = 0;
void Func()
{
for (size_t i = 0; i < 1000000; ++i)
{
LockGuard<mutex> lock(mtx);
++n;
}
}
int main()
{
int begin = clock();
thread t1(Func);
thread t2(Func);
t1.join();
t2.join();
int end = clock();
cout << n << endl;
cout <<"cost time:" <<end - begin << endl;
return 0;
}