1.智能指针解决的问题
int div()
{
int a, b;
cin >> a >> b;
if (b == 0)
throw invalid_argument("除0错误");
return a / b;
}
void Func()
{
// 1、如果p1这里new 抛异常会导致p1不会Delete而导致内存泄漏
// 2、如果p2这里new 抛异常会导致p1和p2都不会delete而导致内存泄漏
// 3、如果div调用这里又会抛异常会导致p1和p2都不会delete而导致内存泄漏
int* p1 = new int;
int* p2 = new int;
cout << div() << endl;
delete p1;
delete p2;
}
int main()
{
try
{
Func();
}
catch (exception& e)
{
cout << e.what() << endl;
}
return 0;
}
综上,我们需要一种可以自动回收内存空间的指针。如果我们把指针套在一种类内,类在析构时会带着指针申请的空间一起析构掉。
class A
{
public:
~A()
{
cout << "~A()" << endl;
}
//private:
int _a1 = 0;
int _a2 = 0;
};
2.智能指针的使用及原理
2.1RAII
RAII(Resource Acquisition Is Initialization)直译:在初始化的时候获取资源。即在对象构造的时候获取资源,在对象的生命周期之中,控制对资源的访问,最后在对象析构的时候释放资源。
好处:
- 不用显示的释放资源(delete)。
- 对象所需的资源,在对象生命周期之内始终有效。
模拟实现1
template<class T>
class SmartPtr {
public:
SmartPtr(T* ptr = nullptr)
: _ptr(ptr)
{}
~SmartPtr()
{
if (_ptr)
{
cout << "Delete:" << _ptr << endl;
delete _ptr;
}
}
private:
T* _ptr;
};
2.2 原理
上述还不能将其称为智能指针,因为它还不具有指针的行为。指针可以解引用,也可 以通过->去访问所指空间中的内容,因此:模板类中还得需要将* 、->重载下,才可让其 像指针一样去使用。
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
智能指针的思想比较简单,但如何拷贝构造是个问题
这里程序崩溃了 原因依然是之前的老问题,对同一块空间进行了两次析构。(别试,崩溃了)
那我们要跟之前一样实现深拷贝吗?不能,因为我们要的就是浅拷贝,要的就是用新的指针来指向我这块资源。
下面我们来看一下C++是如何对这里进行处理。
2.3 auto_ptr
auto_ptr<int>sp2(sp1)
C98里面的大槽点,对拷贝构造的处理沿用了右值引用的资源转移。(可右值本来就是将亡值,给了就给了。左值可不是啊,如果我还需要对sp1进行访问,那就会报访问空指针的错误)。我们将auto_ptr模拟实现一下
namespace chy
{
template<class T>
class auto_ptr
{
public:
//用指针来构造的
auto_ptr(T* ptr = nullptr)
:_ptr(ptr)
{}
//C98中的auto_ptr对于拷贝的问题处理有问题,相当于右值的资源转移
//会把ap里面的资源转移给构造出来的智能指针,而ap将不在管理这些资源(置空)
//所以如果我们再次对与于ap进行解引用操作,将会出现访问空指针的问题
auto_ptr(auto_ptr<T>& ap)
:_ptr(ap._ptr)
{
ap._ptr = nullptr;
}
auto_ptr<T>& operator=(const auto_ptr<T>& ap)
{
if (this != &ap)//如果不是自己给自己赋值
{
if (_ptr)//原本的资源不管了(删除)
{
cout << "Delete" << _ptr << endl;
delete _ptr;
}
_ptr = ap._ptr;
ap._ptr = nullptr;
}
return *this;
}
~auto_ptr()
{
if (_ptr)
{
cout << "Delete:" << _ptr << endl;
delete _ptr;
}
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;// &(*_ptr)
}
private:
T* _ptr;
};
}
拷贝是没问题的 但是不能对sp1再进行解引用操作了
3. C++11的智能指针
3.1 unique_ptr
遇到问题,逃避问题
不让拷贝
unique_ptr(const unique_ptr<T>& ap) = delete;
unique_ptr<T>& operator=(unique_ptr<T>& ap) = delete;
template<class T>
class unique_ptr
{
public:
unique_ptr(T* ptr=nullptr)
:_ptr(ptr)
{}
unique_ptr(const unique_ptr<T>& ap) = delete;
unique_ptr<T>& operator=(unique_ptr<T>& ap) = delete;
~unique_ptr()
{
if (_ptr)
{
cout << "Delete:" << _ptr << endl;
delete _ptr;
}
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
private:
T* _ptr;
};
3.2 share_ptr
引用计数,每有一个指针指向我这块区域,引用计数++,当最后一个指向改位置的智能指针析构时,带着区域一起走。
我这个计数怎么设置?
{
//..
private:
static int count;
//..
}
template<class T>
int shared_ptr<T>::_count = 0;
这样设计可以解决多个指针指向同一块空间的问题。可如果我有两块空间呢?这两块空间的引用计数是不是就变成了同一个了?
所以我们不能纳入静态成员变量,同时我们注意到,我们每指向一块空间,就需要一个单独的计数。那我们在指向空间的时候构造计数不就好了?
而且,由于多个指针共享一个计数,理所应当的成为临界资源,在访问修改时需要加锁!
于是我们在构造函数中下笔
{ //...
shared_ptr(T* sp = nullptr)
: _ptr(sp)
, _pCount(new int(1))
,_pmtx(new std::mutex)
{}
private:
T* _ptr;
int* _pCount;
std::mutex* _pmtx;
}
整体实现
template<class T>
class shared_ptr
{
public:
shared_ptr(T* sp=nullptr)
:_ptr(sp)
,_pCount(new int(1))
,_pmtx(new std::mutex)
{}
shared_ptr(const shared_ptr<T>& sp)
:_ptr(sp._ptr)
,_pCount(sp._pCount)
,_pmtx(sp._pmtx)
{
AddCount();
}
shared_ptr<T>& operator=(const shared_ptr<T>& sp)
{
if (this == &sp) return *this;
Release();
_ptr = sp._ptr;
_pCount = sp._pCount;
_pmtx = sp._pmtx;
AddCount();
return *this;
}
void AddCount()
{
_pmtx->lock();
++(*_pCount);
_pmtx->unlock();
}
void Release()
{
_pmtx->lock();
bool flag = false;
if (--(*_pCount) == 0 && _ptr)
{
cout << "Delete: " << _ptr << endl;
delete _ptr;
delete _pCount;
flag = true;
}
_pmtx->unlock();
if (flag) delete _pmtx;
}
~shared_ptr()
{
Release();
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
//这个接口是给weak_ptr准备的 用来拿到指针
T* get() const
{
return _ptr;
}
private:
T* _ptr;
int* _pCount = 0;
std::mutex* _pmtx;
};
3.2.2 shared_ptr的循环引用问题
函数结束时,n2先析构,然后n1析构。
_next管着右边的节点。 _prev管着左边的节点
_next析构,右边节点就delete。 _prev析构,左边节点就delete
那_next什么时候析构呢?左边的节点被delete,调用析构函数,其成员函数_next就析构了。
那左边的节点什么时候被delete呢? _prev析构的时候,左边的节点析构。
那_prev什么时候析构呢?右边的节点被delete,调用析构函数,其成员函数_prev就析构了。
那左边的节点什么时候被delete呢?........(开始套娃)
3.3 weak_ptr
weak_ptr不是常规的智能指针,没有RAII,不支持直接管理资源。weak_ptr主要用shared_ptr构造,用来解决shared_ptr循环引用问题。
在shared_ptr中新增一个函数,用来返回指针
template<class T>
class shared_ptr
{
public:
//这个接口是给weak_ptr准备的 用来拿到指针
T* get() const
{
return _ptr;
}
private:
T* _ptr;
int* _pCount = 0;
std::mutex* _pmtx;
};
weak_ptr的简单实现
template<class T>
class weak_ptr
{
public:
weak_ptr()
:_ptr(nullptr)
{}
weak_ptr(const shared_ptr<T>& sp)
:_ptr(sp.get())
{}
weak_ptr(const weak_ptr<T>& wp)
:_ptr(wp._ptr)
{}
weak_ptr<T>& operator=(const shared_ptr<T>& sp)
{
_ptr = sp.get();
return *this;
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
private:
T* _ptr;
};