由于使用malloc、new在堆上申请空间后,容易忘记释放对应空间,C++提出了智能指针管理申请的内存,避免内存泄露。
RAII(Resource Acquisition Is Initialization),是一种利用对象生命周期来控制程序资源(如内存、文件句柄、网络连接、互斥量等等)的简单技术。
在对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终保持有效,最后在对象析构的时候释放资源。
借此,我们实际上把管理一份资源的责任托管给了一个对象。这样就不需要显式地释放资源;对象所需的资源在其生命期内始终保持有效。
智能指针就是基于RAII的思想避免内存泄露,本质就是将指针封装为类模板,并通过重载运算符使其具有普通指针的功能。
C++98引入了auto_ptr,采用管理权转移的思想,也就是原对象拷贝给新对象的时候,原对象就会被设置为nullptr,此时就只有新对象指向一块资源空间。
因为原来的对象被设置为nullptr,再次使用原对象会导致程序崩溃,使用并不方便。
auto_ptrunique_ptrptr1(new int(1)); auto_ptr ptr2(ptr1); //ptr1被置为nullptr
既然auto_ptr使用不便,C++11又提出了新的智能指针。
首先是unique_ptr,它采用独占式的思想(和auto_ptr思想有点类似),某个时刻只能有一个unique_ptr指向一个动态分配的资源对象,
也就是这个资源不会被多个unique_ptr对象同时占有,它所管理的资源只能在unique_ptr对象之间进行移动。
它禁用了拷贝构造和赋值,提供move函数用于在unique_ptr对象之间进行移动。
一旦资源脱离unique_ptr(即unique_ptr被重新分配了资源)会被释放,unique_ptr析构时也会释放资源。
int* a = new int(5);
int* b = new int(8);
unique_ptr ptr1(a);
cout<<*a<
shared_ptr
独占式指针并不能满足所有使用场景,多数时候我们都需要多个指针指向同一资源,为了实现共享式的智能指针,C++11提出了shared_ptr。
shared_ptr采用了引用计数的思想,用一个计数指针来计数,当有多个shared_ptr指向同一资源时,他们的计数指针是共享的,释放一个shared_ptr,计数指针减一,
当最后一个shared_ptr被释放时,计数归零,释放资源。
实现shared_ptr
template
class SharedPtr
{
public:
SharedPtr():ptr(nullptr), ptrCount(nullptr), mt(nullptr)
{
}
SharedPtr(T* _ptr):ptr(_ptr)
{
ptrCount = new int(1);
mt = new mutex;
}
//拷贝构造
SharedPtr(SharedPtr& sPtr):ptr(sPtr.ptr), ptrCount(sPtr.ptrCount), mt(sPtr.mt)
{
addCount();
}
void addCount()
{
mt->lock();
(*ptrCount)++;
mt->unlock();
}
void release()
{
cout<<"release"<lock();
if(--(*ptrCount) == 0)
{
cout<<"delete"<unlock();
if(delete_flag)
{
delete mt;
mt = nullptr;
}
}
~SharedPtr()
{
release();
}
//重载赋值, 1.指向同一地址 2.指向不同地址
SharedPtr& operator=(const SharedPtr& sPtr)
{
if(sPtr.ptr != ptr)
{
release(); //先释放原来的
ptr = sPtr.ptr;
ptrCount = sPtr.ptrCount;
mt = sPtr.mt;
addCount();
}
return *this;
}
T& operator*()
{
return *ptr;
}
T* operator->()
{
return ptr;
}
int getCount()
{
return *ptrCount;
}
private:
T* ptr;
int* ptrCount;
mutex* mt;
};
shared_ptr的循环引用问题
首先看一个可以正常释放的shared_ptr
struct ListNode
{
shared_ptr next;
};
int main()
{
shared_ptr node1(new ListNode, [](ListNode* n){
cout<<"delete node1"< node2(new ListNode, [](ListNode* n){
cout<<"delete node2"< node3(nullptr, [](ListNode* n){
cout<<"delete node3"<next = node2;
//node2->next = node1;
cout<<"node1的引用计数"<
如果我们在链表中使用shared_ptr,很容易出现循环引用的问题,如果我们将node1的next指向node2,node2的next指向node1,
那么它们的应用计数都会是2,由于node1和它的next生命周期一样,node2的next不被释放,node1的计数无法减为1,node1无法释放,其next也无法释放,
这导致node2的计数也无法减为1,node2无法释放,其next也无法释放,从而进入了死锁。
int main()
{
shared_ptr node1(new ListNode, [](ListNode* n){
cout<<"delete node1"< node2(new ListNode, [](ListNode* n){
cout<<"delete node2"< node3(nullptr, [](ListNode* n){
cout<<"delete node3"<next = node2;
node2->next = node1;
cout<<"node1的引用计数"<
要解决这个问题需要借助weak_ptr
weak_ptr
由于shared_ptr存在循环引用问题,C++11提供了weak_ptr服务shared_ptr。
weak_ptr不会增加引用计数,不影响对象的生命周期,C++11标准虽然将 weak_ptr 定位为智能指针的一种,但该类型指针通常不单独使用(没有实际用处),只能和 shared_ptr 类型指针搭配使用。甚至于,我们可以将 weak_ptr 类型指针视为 shared_ptr 指针的一种辅助工具,借助 weak_ptr 类型指针, 我们可以获取 shared_ptr 指针的一些状态信息,比如有多少指向相同的 shared_ptr 指针、shared_ptr 指针指向的堆内存是否已经被释放等等。
weak_ptr 模板类中没有重载 * 和 -> 运算符,这也就意味着,weak_ptr 类型指针只能访问所指的堆内存,而无法修改它。
由于weak_ptr指向的对象可能被释放,所以提供了expired()函数来判断所指对象是否已经被销毁。
将前面循环引用问题结构体内改为weak_ptr即可解决。
struct ListNode
{
weak_ptr next;
};



