文章目录
1.为什么需要智能指针
程序员自己控制的内存,在一些特殊场景之下,有可能发生内存泄漏,智能指针则可以帮助对内存进行管理
比如程序员忘记释放内存,异常的出现,可能会导致内存泄漏
智能指针采用的RAII思想 -> 利用对象的生命周期来控制程序资源,在对象构建的时候获取资源,在对象析构的时候释放资源,即将对资源的管理工作,托付给了一个对象,这样做的好处是:不需要显示的释放资源、对象所需要的资源,在其生命周期内始终保持有效
template<class T>
class smartPtr
{
public:
smartPtr(T *ptr)
:_Ptr(ptr)
{}
~smartPtr()
{
cout << "~smartPtr()" << endl;
delete[] _Ptr;
}
private:
T *_Ptr;
};
void test()
{
while (1)
{
int *ptr = new int;
//使用ARII思想,管理资源,如果没有,当抛异常的时候,并不会释放资源
//就会导致内存泄漏
smartPtr<int> sptr(ptr);
throw 1;
//delete ptr;
}
}
void Ptrtest()
{
while (1)
{
try
{
test();
}
catch (...)
{
cout << "catch (...) " << endl;
}
}
}
2.三种智能指针使用及原理分析
2.1 auto_ptr
1.使用立即初始化,防止二次释放
2.拷贝和赋值会导致资源管理权转移,再次解引用就会造成空指针的访问
3.由于auto_ptr 的拷贝和赋值拷贝都会出现资源转移的情况,即使用的时候很容易出错,因此实际之中,一般不允许使用
4.模拟实现
template<class T>
class Auto_ptr
{
public:
Auto_ptr(T *ptr)
:_ptr(ptr)
{}
~Auto_ptr()
{
cout << "~Auto_ptr()" << endl;
delete[] _ptr;
}
//管理权转移
Auto_ptr(Auto_ptr<T>& ap)
:_ptr(ap._ptr)
{
ap._ptr = nullptr;
}
Auto_ptr<T>& operator =(Auto_ptr<T>& ap)
{
if (this != &ap)
{
//释放资源
if (_ptr)
delete _ptr;
_ptr = ap._ptr;
ap._ptr = nullptr;
}
return *this;
}
T& operator *()
{
return *_ptr;
}
T* operator ->()
{
return _ptr;
}
private:
T *_ptr;
};
class Data
{
public:
Data(int day)
:_day(day)
{}
int _day;
};
2.2 unique_ptr
既然auto_ptr的拷贝和赋值会发生资源转移,那么unique_ptr,就禁止拷贝和赋值的发生,即在拷贝和赋值成员函数后面加上delete
模拟实现:
template<class T>
class Unique_ptr
{
public:
Unique_ptr(T *ptr)
:_ptr(ptr)
{}
~Unique_ptr()
{
delete[] _ptr;
}
T& operator *()
{
return *_ptr;
}
T* operator ->()
{
return _ptr;
}
//禁止拷贝和赋值
Unique_ptr(Unique_ptr<T>& up)=delete;
Unique_ptr<T>& operator =(Unique_ptr<T>& up)=delete;
private:
T *_ptr;
};
2.3shared_ptr
2.3.1原理
auto_ptr 的拷贝和赋值存在不安全因素,unnique_ptr直接禁止了拷贝和赋值
如果智能指针需要拷贝和赋值呢,这时候,就得看shared_ptr了
如何实现:
我们可以给定一个资源计数器,当前资源被多少个智能指针管理,计数器则为几,当进行析构的时候,计数器–,当计数器为0的时候,表示当前资源没有被管理了,则对该资源进行delete
template<class T>
class Shared_ptr
{
public:
Shared_ptr(T *ptr)
:_ptr(ptr)
, _CountPtr(new int(1))//构造的时候,被一个管理起来
{}
~Shared_ptr()
{
(*_CountPtr)--;
if (*_CountPtr == 0)//为0时,表示没有管理的了
{
delete _ptr;
delete _CountPtr;
}
}
Shared_ptr(const Shared_ptr<T> &sp)
:_ptr(sp._ptr)
, _CountPtr(sp._CountPtr)
{
(*_CountPtr)++;
}
Shared_ptr<T> &operator=(const Shared_ptr<T> &sp)
{
//判断是不是管理的同一份资源
if (_ptr != sp._ptr)
{
if (--(*_CountPtr) == 0)//断开已经管理的资源
{
delete _ptr;//如果管理的资源没有其它人进行管理了,则释放掉
delete _CountPtr;
}
//赋值
_ptr = sp._ptr;
_CountPtr = sp._CountPtr;
(*_CountPtr)++;
}
return *this;
}
T& operator *()
{
return *_ptr;
}
T* operator ->()
{
return _ptr;
}
int GetCount()
{
return *_CountPtr;
}
private:
T *_ptr;
int *_CountPtr;//通过指针,来控制管理同一份资源的,都指向同一块地址空间
};
2.3.2线程安全问题
多线程之中,在进行拷贝和赋值的时候,线程计数器就变成了临界资源,这时候,就需要对线程计数器进行加锁处理,否则会发生线程安全问题
class Data
{
public:
Data(int day)
:_day(day)
{}
int _day;
};
void func(Shared_ptr<Data> &sp,int n)
{
for (int i = 0; i < n; i++)
{
Shared_ptr<Data> sp2(sp);//进行n次拷贝
}
}
void test()
{
Shared_ptr<Data> sp(new Data(1000));
cout << "begin:" << sp.GetCount() << endl;
int n;
cin >> n;
thread t1(func, ref(sp), n);//使用ref线程函数的参数是以值拷贝的方式到线程栈空间的
thread t2(func, ref(sp), n);
t1.join();
t2.join();
cout << "end:" << sp.GetCount() << endl;
}
加锁后的代码和实验效果:
template<class T>
class Shared_ptr
{
public:
Shared_ptr(T *ptr)
:_ptr(ptr)
, _CountPtr(new int(1))//构造的时候,被一个管理起来
, _mtx(new mutex)//初始化一把锁
{}
~Shared_ptr()
{
subRef();//加锁后的--操作
//(*_CountPtr)--;
if (*_CountPtr == 0)//为0时,表示没有管理的了
{
delete _ptr;
delete _CountPtr;
delete _mtx;
cout << "~Shared_ptr()" << endl;
}
}
Shared_ptr(const Shared_ptr<T> &sp)
:_ptr(sp._ptr)
, _CountPtr(sp._CountPtr)
, _mtx(sp._mtx)
{
addRef();//加锁后的++操作
//(*_CountPtr)++;
}
Shared_ptr<T> &operator=(const Shared_ptr<T> &sp)
{
//判断是不是管理的同一份资源
if (_ptr != sp._ptr)
{
//if (--(*_CountPtr) == 0)//断开已经管理的资源
if (subRef==0)
{
delete _ptr;//如果管理的资源没有其它人进行管理了,则释放掉
delete _CountPtr;
delete _mtx;
}
//赋值
_ptr = sp._ptr;
_CountPtr = sp._CountPtr;
_mtx = sp._mtx;
addRef();
}
return *this;
}
T& operator *()
{
return *_ptr;
}
T* operator ->()
{
return _ptr;
}
int GetCount()
{
return *_CountPtr;
}
int addRef()
{
_mtx->lock();
(*_CountPtr)++;
_mtx->unlock();
return *_CountPtr;
}
int subRef()
{
_mtx->lock();
(*_CountPtr)--;
_mtx->unlock();
return *_CountPtr;
}
private:
T *_ptr;
int *_CountPtr;//通过指针,来控制管理同一份资源的,都指向同一块地址空间
mutex *_mtx;//加上一把锁
};
2.3.3循环引用问题
template <class T>
struct ListNode
{
shared_ptr<ListNode<T>> _next;
shared_ptr<ListNode<T>> _prev;
~ListNode()
{
cout << " ~ListNode()" << endl;
}
};
void test()
{
shared_ptr<ListNode<int>> sp(new ListNode<int>);
shared_ptr<ListNode<int>> sp2(new ListNode<int>);
cout << "begin:" << sp.use_count() << " " << sp2.use_count() << endl;
sp->_next = sp2;
sp2->_prev = sp;
cout << "end:" << sp.use_count() << " " << sp2.use_count() << endl;
}
解决办法:
使next和prev管理资源的时候,计数器不会增加,这样就不会出现内存泄漏的问题了
于是有了weak_ptr智能指针,不会增加计数器
template <class T>
class weak_ptr
{
public:
weak_ptr()
:ptr=nullptr
{}
weak_ptr(const shared_ptr<T> &p)
:ptr(p.ptr)
{}
weak_ptr<T>& operator =(const shared_ptr<T> &p)
{
if (*this!=p)
ptr = p.ptr;
return *this;
}
T& operator *()
{
return *ptr;
}
T* operator()
{
return ptr;
}
private:
T *ptr;
};
}
emplate <class T>
struct ListNode
{
weak_ptr<ListNode<T>> _next;//使用weak_ptr,不会增加计数器的值
weak_ptr<ListNode<T>> _prev;
~ListNode()
{
cout << " ~ListNode()" << endl;
}
};
2.3.4删除问题
shared_ptr 析构函数中资源的释放,默认是使用delete的,如果我们的对象时malloc出来的,或者是new出来的一整块空间,该怎么办呢?
shared_ptr之中提供了一个删除器(仿函数),可以通过自己传入的仿函数进行资源的释放
template<class T,class Del>
class Shared_ptr
{
public:
Shared_ptr(T *ptr,Del del)
:_ptr(ptr)
, _CountPtr(new int(1))//构造的时候,被一个管理起来
, _mtx(new mutex)//初始化一把锁
, _del(del)
{}
~Shared_ptr()
{
subRef();//加锁后的--操作
//(*_CountPtr)--;
if (*_CountPtr == 0)//为0时,表示没有管理的了
{
//delete _ptr;
_del(_ptr);//使用删除器来释放资源
delete _CountPtr;
delete _mtx;
cout << "~Shared_ptr()" << endl;
}
}
Shared_ptr(const Shared_ptr<T,Del> &sp)
:_ptr(sp._ptr)
, _CountPtr(sp._CountPtr)
, _mtx(sp._mtx)
{
addRef();//加锁后的++操作
//(*_CountPtr)++;
}
Shared_ptr<T, Del> &operator=(const Shared_ptr<T, Del> &sp)
{
//判断是不是管理的同一份资源
if (_ptr != sp._ptr)
{
//if (--(*_CountPtr) == 0)//断开已经管理的资源
if (subRef==0)
{
del(ptr);//使用删除器来释放资源
//delete _ptr;//如果管理的资源没有其它人进行管理了,则释放掉
delete _CountPtr;
delete _mtx;
}
//赋值
_ptr = sp._ptr;
_CountPtr = sp._CountPtr;
_mtx = sp._mtx;
addRef();
}
return *this;
}
T& operator *()
{
return *_ptr;
}
T* operator ->()
{
return _ptr;
}
int GetCount()
{
return *_CountPtr;
}
int addRef()
{
_mtx->lock();
(*_CountPtr)++;
_mtx->unlock();
return *_CountPtr;
}
int subRef()
{
_mtx->lock();
(*_CountPtr)--;
_mtx->unlock();
return *_CountPtr;
}
private:
T *_ptr;
int *_CountPtr;//通过指针,来控制管理同一份资源的,都指向同一块地址空间
mutex *_mtx;//加上一把锁
Del _del;//删除其
};
class A
{
public:
A()
{}
~A()
{
cout << "~A()" << endl;
}
private:
};
template<class T>//定制删除器
struct Del
{
void operator()(T* a)
{
delete[] a;
}
};
void test()
{
Del<A> del;
Shared_ptr<A,Del<A>> sp(new A[10],del);
}
3.总结
智能指针的实现:RAII思想,函数重载模拟指针功能
1.auto_ptr:
发生拷贝或者赋值时,管理权转移
潜在问题:发生拷贝之后,不能继续访问资源,会有解引用问题
一般禁止使用
2.unique_ptr:
防拷贝、防赋值
可以使用:不涉及智能指针拷贝和赋值问题
3.shared_ptr:
通过引用计数,解决拷贝问题
拷贝式,引用计数自增
析构时,引用计数自减,自减之后,如果引用计数器为0,则释放资源
特殊场景之下,会发生循环引用问题,通过weak_ptr解决
shared_ptr自身是线程安全的,但是管理的资源需要用户自己保证线程安全
shared_ptr 默认通过delete释放资源,也可以用户传入仿函数进行删除