为什么需要智能指针
在写代码的时候,从堆上申请的空间,由于一些原因(代码中途异常),没有得到释放,从而导致代码出现内存泄漏,所以为了防止此类问题的出现,从而出现智能指针。采用智能指针可以自动检测,指针如果不用,则会自动释放堆上的空间。
智能指针作用
管理指针,自动释放资源。
RAII
RAII是一种利用对象生命周期来控制程序资源的技术
在类中,对象构造的时候获取资源,最后在对象析构后,释放资源,这样就不用担心内存资源没有被释放。如果将申请资源的指针交给类来管理(管理:释放资源),就在该函数结束时,编译器自动调用类的析构函数,完成对类管理资源的释放。实际上就是将指针交给类进行管理,在构造函数中申请资源,在析构函数中,释放资源。
- 优点
不用显示释放资源
对象在使用中资源始终有效
同时RAII,还需要让该类具有像指针一样的方式,只需要对* 与->两个运算符重载。
缺点:RAII存在浅拷贝问题。
template<class T>
class Smartptr
{
public:
Smartptr(T *ptr = nullptr)
:_ptr(ptr)
{
}
~Smartptr()
{
if(_ptr)
delete _ptr;
}
private:
T* _ptr;
};
void testSmartptr()
{
int *p = new int;
Smartptr<int> sp(p);
}
智能指针的原理
上面讲的RAII的实现方法,还不能算智能指针,因为其少了指针的几个特有的操作
- 解引用*
- 指向操作 ->
因此只需要将上面的两种操作在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;
};
struct Date
{
int a;
int b;
};
void testSmartptr()
{
Smartptr<int> sp(new int); //sp相当于一个对象,用来管理资源
*sp = 10;
cout << *sp << endl;
Smartptr<Date> sp2(new Date);
sp2->a = 1;
sp2->b = 2;
cout << sp2->a << sp2->b << endl;
}
上面这种方法在拷贝构造函数与赋值运算符重载方法中不可行,容易产生浅拷贝。但是不能使用深拷贝来解决,因为深拷贝会产生两块空间,同时,资源是外部用户提供的,智能指针没有申请空间的权限,只能对资源进行管理。
- C++98 中 autoPtr原理
在autoPtr库函数中,实现原理就是在拷贝构造函数与赋值运算符重载中,将后面对象的内容转移到前面对象里面去,最后将后面的对象内容置空。
template<class T>
class AutoPtr
{
public:
AutoPtr(T* ptr)
:_ptr(ptr)
{
}
~AutoPtr()
{
if (_ptr)
delete _ptr;
}
AutoPtr(AutoPtr<T>&sp) //拷贝构造函数
:_ptr(sp._ptr)
{
sp._ptr = nullptr;
}
AutoPtr<T>& operator=(AutoPtr<T>&sp)
{
if (*this != sp) //先判断是否自己给自己赋值
{
if (_ptr) //如果赋值等号前面的有空间,需将其释放
delete _ptr;
_ptr = sp._ptr;
sp._ptr = nullptr;
}
return *this;
}
AutoPtr* operator->()
{
return this;
}
AutoPtr& operator*()
{
return *this;
}
private:
T* _ptr;
};
因为发生深拷贝与赋值时,只能使用一份,所以c++标准委员会不建议使用。
auto_ptr的改进版本:
实现原理:增加一个参数,bool owner,用来管理资源释放的权利。
if (_ptr && _owner) //资源存在,同时拥有资源释放的权利
delete _ptr;
template<class T>
class auto_ptr
{
public:
auto_ptr(T* ptr = nullptr)
:_ptr(ptr)
, _owner(false)
{
if (_ptr)
_owner = true;
}
auto_ptr( auto_ptr<T>& p)
:_ptr(p._ptr)
,_owner(p._owner)
{
p._owner = false;
}
auto_ptr& operator*()
{
return *_ptr;
}
auto_ptr* operator->()
{
return _ptr;
}
auto_ptr<T>& operator = (auto_ptr& p)
{
if (this != &p)
{
if (_ptr && _owner)
delete _ptr;
_ptr = p._ptr;
_owner = p._owner;
p._owner = false;
}
return *this;
}
~auto_ptr()
{
if (_ptr && _owner)
delete _ptr;
}
private:
T* _ptr;
bool _owner;
};
auto_ptr的改性版本可能会造成野指针。
- unique_ptr库函数,因为智能指针容易产生浅拷贝,所以就禁止拷贝构造函数与赋值运算符的重载,将拷贝构造函数和赋值运算符重载写成私有成员函数。
template<class T>
class Uniqueptr
{
public:
Uniqueptr(T* ptr = nullptr)
:_ptr(ptr)
{
}
~Uniqueptr()
{
if (_ptr)
delete _ptr;[]
}
Uniqueptr* operator->()
{
return this;
}
Uniqueptr& operator*()
{
return *this;
}
Uniqueptr(Uniqueptr<T>const &) = delete;
Uniqueptr<T>& operator = (Uniqueptr<T>const &) = delete;
private:
T* _ptr;
};
- shared_ptr
就是通过计数方式实现资源的共享。通过计数的方式来判断是否需要进行释放资源,shared_ptr,给每份资源维护了一个计数,用来记录每份资源被几个对象共享,在对象调用析构函数时,计数-1,当计数减到0时,表示最后一份资源不适用了,最后释放该资源。当计数不是0时表示该资源还在使用中,不能释放该资源。
shared_ptr = RAII + operator* +opeartor-> + 计数
#include<mutex> //并发程序互斥锁
template<class T>
class shared_ptr
{
public:
shared_ptr(T *ptr)
:_ptr(ptr)
,_pcount(new int(1))
//,_pmutex(new mutex)
{
}
~shared_ptr()
{
Release();
}
shared_ptr(const shared_ptr<T> &sp)
:_ptr(sp._ptr)
,_pcount(sp._pcount)
//,_pmutex(sp._pmutex)
{
addcount();
}
shared_ptr<T>& operator=(const shared_ptr<T> &sp)
{
if (_ptr != sp._ptr)
{
Release(); //释放旧空间
_ptr = sp._ptr;
_pcount = sp._pcount;
addcount(); //计数+1
//_pmutex = sp._pmutex;
}
return *this;
}
T& operator*()
{
return *this;
}
T* operator->()
{
return this;
}
void addcount()
{
//_pmutex->lock(); //加锁
++(*_pcount); //计数+1
//_pmutex->unlock();//解锁
}
void Release()
{
//bool deleteflag = false;
if (0 == --(*_pcount))
{
delete _ptr;
delete _pcount;
}
/*if (deleteflag == ture)
{
delete _pmutex;
}*/
}
int usecount()
{
return *_pcount;
}
private:
T * _ptr;
int *_pcount;
//mutex* _pmutex; 多线程加锁
};
shared_ptr改进,由于shared_ptr,所管理的指针有多重申请方式,导致不能将释放函数写成固定的,所以析构函数需要模板特化。
//定制删除器
template<class T>
class DFDel //默认new出来的空间
{
public:
void operator()(T*& p)
{
if (p)
{
delete p;
p = nullptr;
}
}
};
template<class T>
class Free //malloc申请的空间
{
public:
void operator()(T*& p)
{
if (p)
{
free(p);
p = nullptr;
}
}
};
class Fclose
{
public:
void operator()(FILE* p)
{
if (p)
{
fclose(p);
p = nullptr;
}
}
};
namespace bai
{
template<class T,class DF = DFDel<T>> //DF为删除类型,DF默认为new出来的空间
class shared_ptr
{
public:
shared_ptr(T* ptr = nullptr)
:_ptr(ptr)
{
if (_ptr)
_pcount = new int(1);
}
shared_ptr(const shared_ptr<T>& p)
:_ptr(p._ptr)
, _pcount(p._pcount)
{
if (_ptr) // 如果资源不为nullptr
++(*_pcount);
}
//p1 == p2
//p1:未管理资源------直接p2共享
//p1:单独管理资源----在于p2共享之前先释放自己的资源
//p1:与其他对象共享资源---p1计数--,p2计数++
shared_ptr<T> operator = (const shared_ptr<T>& p)
{
if (this != &p)
{
if (_ptr && 0 == --(*_pcount))
{
delete _ptr;
delete _pcount;
}
_ptr = p._ptr;
_pcount = p._pcount;
if (_ptr)
++(*_pcount);
}
return *this;
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
~shared_ptr()
{
if (_ptr && --(*_pcount) == 0) //由于资源的申请方式不同,
{
//所以需要根据资源的类型,定制释放的方式
//delete _ptr;
DF()(_ptr); //DF相当于一种类型,DF()相当于创建一个无名的对象,DF()(_ptr),相当于DF对象调用
delete _pcount;
}
}
int usecount()
{
return *_pcount;
}
private:
T* _ptr;
int* _pcount;
};
}
void TestFunc()
{
bai::shared_ptr<int,DFDel<int>> p1(new int);
bai::shared_ptr<int,DFDel<int>> p2(p1);
bai::shared_ptr<FILE, Fclose> p3(fopen("666.text" ,"rb"));
}
shared_ptr的缺陷:
当使用智能指针管理双向链表时,容易产生循环引用。
struct ListNode
{
ListNode(int data = int())
:_pre(nullptr)
, _next(nullptr)
{
cout << "ListNode()" << this << endl;
}
~ListNode()
{
cout << "~ListNode()" << endl;
}
shared_ptr<ListNode> _pre;
shared_ptr<ListNode> _next;
int data;
};
void TestFunc()
{
shared_ptr<ListNode> p1(new ListNode(10));
shared_ptr<ListNode> p2(new ListNode(20));
cout << p1.use_count() << endl;//查看p1中的引用计数
cout << p2.use_count() << endl;
p1->_next = p2;
p2->_pre = p1;
cout << p1.use_count() << endl;
cout << p2.use_count() << endl;
}
没有调用析构函数,导致资源泄露
解决shared_ptr循环引用
weak_ptr:不能单独管理资源,必须配合shared_ptr一起使用。
weak_ptr:就是为了解决shared_ptr存在的循环引用
struct ListNode
{
ListNode(int _data = int())
:data(_data)
{
cout << "ListNode()" << this << endl;
}
~ListNode()
{
cout << "~ListNode()" << endl;
}
weak_ptr<ListNode> _pre; //****
weak_ptr<ListNode> _next;
int data;
};
因为weak_ptr不能单独管理资源,所以构造函数不能给_pre,_next初始化