文章目录
- 前言
- 为什么用智能指针
- 智能指针简单实现
- unique_ptr
- shared_ptr
- 循环引用和weak_ptr的引入
- 循环引用
- weak_ptr
- 定制删除器
前言
大家好久不见,今天来学习有关智能指针的内容~
为什么用智能指针
假如我们有如下场景:
double Div()
{
int x, y;
cin >> x >> y;
if (y == 0)
throw "div Exception cause div 0!!!";
else
return (double)x / (double)y;
}
int main()
{
int* p1 = new int;
int* p2 = new int;
Div();
delete p1;
delete p2;
}
由于p1、p2、都需要释放,因此一旦在p2、p3出现了异常我们要手动释放前面的资源,这样的方式特别麻烦,这里还仅仅只是两个资源,一旦涉及更多new的资源会更麻烦,为了解决这个问题,c++引入了智能指针解决这个问题。
智能指针简单实现
一般而言智能指针要有三个问题:
1、利用对象的生命周期来控制程序资源,RAII的思想。
2、像指针一样使用。
3、考虑拷贝的问题。
unique_ptr
上面的例子中,如果p2、div出现了问题,前面的资源就无法释放,虽然可以通过重新抛出的方式来解决,但让代码可读性变得很差,同时也非常麻烦。使用智能指针可以解决这个问题,下面是一个智能指针的实例:
template<class T>
class SmartPtr
{
public:
//构造保存指针
SmartPtr(T* ptr)
: _ptr(ptr)
{
}
//析构释放资源
~SmartPtr()
{
if(_ptr)
delete _ptr;
cout << "delete _ptr success !" << endl;
}
//模拟指针的两个行为
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
private:
T* _ptr;
};
这样完美地达到了控制资源释放的要求,但与此同时也引来了一个新的问题,即原生指针是可以拷贝的,但智能指针显然不可以拷贝,因为这里拷贝我们要求浅拷贝,那样对象在释放时同一份资源就会析构两次,这是非常可怕的。
为了解决这个问题,c++98库在实现auto_ptr的时候,使用了一种叫管理权转移的方式,如下代码,但其他这样效果非常不好。
//拷贝的悬空
SmartPtr(SmartPtr& sp)
: _ptr(sp._ptr)
{
sp._ptr = nullptr;
}
在后来的c++准标准库中,boost设计了一种新的智能指针,在c++11中相当于unique_ptr,该指针直接明令禁止不允许拷贝。
禁止别人拷贝的方式有很多,这里介绍两种:
在c++98中,一般只声明不实现,为了防止有人在类外动手脚,需要再用private封死这两个函数;相比之下c++11就更加简单,只需要写上delete关键字即可。
//c++11拷贝赋值禁止
unique_ptr(const unique_ptr<T>& up) = delete;
unique_ptr<T>& operator=(const unique_ptr<T>& up) = delete;
//c++98禁止:
private:
unique_ptr(const unique_ptr<T>& up);
unique_ptr<T>& operator=(const unique_ptr<T>& up);
shared_ptr
可以看出上面解决拷贝问题的方式本质就是规避了这个问题,shared_ptr不同,他允许我们进行拷贝构造。
要解决拷贝问题,实际上就是要控制好对象何时释放资源、释放几次的问题,我们发现引用计数很适合解决这个问题。
template<class T>
class shared_ptr
{
public:
//构造保存指针
shared_ptr(T* ptr)
: _ptr(ptr)
, _count(new int(1))
, _pmtx(new mutex)
{
}
//拷贝
shared_ptr(const shared_ptr<T>& sp)
: _ptr(sp._ptr)
{
if (_ptr != sp._ptr)
{
sp._count++;
}
}
//析构释放资源
~shared_ptr()
{
if ((--(*_count) == 0))
{
delete _ptr;
delete _count;
cout << "delete _ptr;" << " delete _count; " << endl;
}
cout << "delete _ptr success !" << endl;
}
//模拟指针的两个行为
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
private:
T* _ptr = nullptr;
int* _count;
mutex* _pmtx;
};
上述代码简单模拟了shared_ptr,但我们发现一旦使用了引用计数,不可避免地会出现线程安全问题,为了解决线程安全的问题,我们要对这些地方加锁。
template<class T>
class shared_ptr
{
public:
//构造保存指针
shared_ptr(T* ptr=nullptr)
: _ptr(ptr)
, _count(new int(1))
, _pmtx(new mutex)
{
}
// +
void AddCount()
{
_pmtx->lock();
++(*_count);
_pmtx->unlock();
}
// -
void Release()
{
_pmtx->lock();
bool deleteFlag = false;
if (--(*_count) == 0)
{
delete _ptr;
delete _count;
cout << "delete " << _ptr << endl;
deleteFlag = true;
}
_pmtx->unlock();
if (deleteFlag)
delete _pmtx;
}
//拷贝构造
shared_ptr(const shared_ptr<T>& sp)
: _ptr(sp._ptr)
, _count(sp._count)
, _pmtx(sp._pmtx)
{
AddCount();
}
//赋值
shared_ptr<T>& operator=(const shared_ptr<T>& sp)
{
if (_ptr != sp._ptr)
{
//这里是赋值之前的-
Release();
_ptr = sp._ptr;
_count = sp._count;
_pmtx = sp._pmtx;
//注意这里加就是赋值之后的加了
AddCount();
}
return *this;
}
//析构释放资源
~shared_ptr()
{
Release();
}
//模拟指针的两个行为
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
T* get() const
{
return _ptr;
}
int use_count()
{
return *_count;
}
private:
T* _ptr;
int* _count;
mutex* _pmtx;
};
注意,shared_ptr本身是线程安全的,但管理的对象并不是线程安全的,需要加锁保护,在一些极端的场景下还会出现循环引用的问题。
循环引用和weak_ptr的引入
循环引用
struct ListNode
{
int _val;
nhy::shared_ptr<ListNode> _prev;
nhy::shared_ptr<ListNode> _next;
~ListNode()
{
cout << "~ListNode" << endl;
}
};
如果链表的两个指针也使用shared_ptr,就会出现如图所示循环引用的问题,不仅仅p指向这个对象,还有一个next或prev也指向这个对象,这样双方会僵持不下,谁都无法释放。
weak_ptr
要解决这个问题,只需要将不必要的计数功能取消即可,其实weakptr本质就是sharedptr取消了计数功能。
template<class T>
class weak_ptr
{
public:
weak_ptr()
:_ptr(nullptr)
{}
weak_ptr(const shared_ptr<T>& sp)
:_ptr(sp.get())
{}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
T* get()
{
return _ptr;
}
private:
T* _ptr;
};
定制删除器
可以传入一个对象来管理释放资源。
template<class D>
shared_ptr(T* ptr, D del)
: _ptr(ptr)
, _count(new int(1))
, _pmtx(new mutex)
, _del(del)
{
}
function<void(T*)> _del = [](T* ptr) {
cout << "default delete" << endl;
delete ptr;
};
// 定制删除器 -- 可调用对象
template<class T>
struct DeleteArray
{
void operator()(T* ptr)
{
cout << "void operator()(T* ptr)" << endl;
delete[] ptr;
}
};
class Date
{
private:
int _year;
int _month;
int _day;
};
void test_delete()
{
nhy::shared_ptr<int> spa1(new int[10],DeleteArray<int>());
nhy::shared_ptr<Date> spa2(new Date[10]);
nhy::shared_ptr<Date> spa3(new Date[10],DeleteArray<Date>());
}