文章目录
- 一.什么是RAII内存管理技术?
- 二.智能指针
- unique_ptr
- shared_ptr
- 循环引用问题
- weak_ptr
一.什么是RAII内存管理技术?
- C++在引入异常机制后,代码执行流的跳转变得难以预料,如果使用普通的指针进行内存管理,很难避免内存泄漏的问题(执行流跳转导致堆区资源无法被释放)
- RAII技术指的是利用对象的生命周期来管理内存资源,就堆区内存资源的管理而言,指的就是:将指针封装在类中,在类对象构造时获取堆区资源,当类对象生命周期结束时,通过类对象的析构函数自动完成堆区资源的释放,这样的类对象就是智能指针
- 智能指针可以有效地避免开发中出现内存泄漏的问题,同时为开发者省去了很多时间和精力
二.智能指针
- C++11标准库中智能指针主要分为三大类:
unique_ptr
unique_ptr
对象之间不允许进行拷贝,即一个unique_ptr
对象管理一块堆区内存资源,是一一对应的关系unique_ptr
的简单实现原理:
//不允许拷贝的智能指针
//delfunc是堆区资源释放函数,用于调用delete或者delete[]
template<class T,class delfunc>
class unique_ptr
{
public:
unique_ptr(T * ptr = nullptr)
:_ptr(ptr)
{}
~unique_ptr()
{
if (_ptr)
{
delfunc del;
del(_ptr);
std::cout << "delete" << std::endl;
}
}
//让智能指针可以像指针一样被使用
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
//禁止unique_ptr对象间的拷贝
unique_ptr(const unique_ptr<T, delfunc>& unptr) = delete;
unique_ptr<T, delfunc>& operator=(const unique_ptr<T, delfunc>& unptr) = delete;
private:
T* _ptr;
};
- 使用指针管理堆区资源时,我们会让其指向单个对象或者对象数组,单个对象用
delete
完成资源释放,而对象数组要采用delete[]
完成资源释放.unique_ptr
的类模板参数delfunc
的设计目的就是为了区分上述两种情况,让使用者通过设计仿函数的方式在delete
和delete[]
之间做选择
shared_ptr
shared_ptr
是允许进行拷贝的智能指针,然而shared_ptr
对象之间发生拷贝时,就会出现多个智能指针对象同时管理同一块堆区内存的情况,shared_ptr
通过引用计数的技术避免了同一块堆区资源被释放多次的情况出现:- 引用计数的实现思路:
- 在
shared_ptr
内部封装一个指针int * recount
用来维护引用计数变量 - 每当一个
shared_ptr
对象进行构造并申请堆区资源时,同时在堆区申请一个int变量作为该shared_ptr
对象所指向的堆区资源的引用计数变量 - 每当
shared_ptr
被拷贝时,连同int * recount
一起拷贝,然后让引用计数变量加一即可(赋值重载的实现还要考虑引用计数变量减一和堆区资源释放的问题) - 每当
shared_ptr
析构时,引用计数变量减一,若引用计数变量减为0,则释放堆区资源
- 在
shared_ptr
的简单实现原理:
//允许拷贝的智能指针,利用引用计数解决内存管理冲突
//delfunc是堆区资源释放函数,用于调用delete或者delete[]
template<class T, class delfunc>
class shared_ptr
{
public:
//构造智能指针(同时创建引用计数变量)
shared_ptr(T* ptr = nullptr)
:_ptr(ptr),
_recount(new int(1))
{}
shared_ptr(const shared_ptr<T, delfunc>& shptr)
: _ptr(shptr._ptr),
_recount(shptr._recount)
{
//智能指针拷贝增加引用计数
(*_recount)++;
}
shared_ptr<T, delfunc>& operator=(const shared_ptr<T, delfunc>& shptr)
{
if (_ptr != shptr._ptr)
{
//智能指针指向改变,其先前指向的堆区内存和引用计数变量需要处理
Release();
//完成智能指针拷贝并增加引用计数
_ptr = shptr._ptr;
_recount = shptr._recount;
++(*_recount);
}
return (*this);
}
~shared_ptr()
{
Release();
}
//让智能指针可以像指针一样被使用
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
T* getptr() const
{
return _ptr;
}
private:
//将资源释放函数进行封装,方便复用
//引用计数减一,若引用计数减为0则释放资源
void Release()
{
//引用计数减一
--(*_recount);
//引用计数为0则释放资源
if (*_recount == 0)
{
if (_ptr)
{
delfunc del;
del(_ptr);
std::cout << "delete" << std::endl;
}
//同时注意释放引用计数变量
delete _recount;
}
}
private:
T* _ptr;
int* _recount;
};
shared_ptr
类内部同时还要解决引用计数变量带来的线程安全的问题,这里暂不讨论
循环引用问题
shared_ptr
用在自引用结构体中会出现对象无法析构的问题:
template<class T>
class deletefunc
{
public:
void operator()(T* ptr)
{
delete ptr;
}
};
// 循环引用
struct ListNode
{
int _val;
shared_ptr<ListNode,deletefunc<ListNode>> _next;
shared_ptr<ListNode,deletefunc<ListNode>> _prev;
~ListNode()
{
cout << "~ListNode()" << endl;
}
};
// 循环引用
void test_shared_cycle()
{
share_ptr<ListNode, deletefunc<ListNode>> n1(new ListNode);
share_ptr<ListNode, deletefunc<ListNode>> n2(new ListNode);
n1->_next = n2;
n2->_prev = n1;
}
- 当
test_shared_cycle()
函数执行完后,两个链表节点都无法正常析构,shared_ptr
引发了循环引用的问题: n1
节点的析构依赖于n2
节点中_prev
指针的析构,n2
节点中_prev
指针的析构依赖于n2
节点的析构,n2
节点的析构又依赖于n1
节点中_next
指针的析构,n1
节点中_next
指针的析构又依赖于n1
节点的析构.如此往复构成了循环的依赖关系导致两个节点都无法被释放.weak_ptr
就是为了解决循环引用问题而设计的
weak_ptr
weak_ptr
智能指针不参与堆区内存的申请和释放,也不会增加特定内存块的引用计数(其功能和普通指针差不多),weak_ptr
支持以shared_ptr
为引用形参的拷贝构造和赋值weak_ptr
简单实现原理:
//用于配合share_ptr的使用用于循环引用的场景
//delfunc是堆区资源释放函数,用于调用delete或者delete[]
//weak_ptr不参与资源的申请和释放
template<class T, class delfunc>
class weak_ptr
{
public:
weak_ptr()
:_ptr(nullptr)
{}
~weak_ptr()
{}
weak_ptr(const weak_ptr<T, delfunc>& wptr)
:_ptr(wptr._ptr)
{}
weak_ptr(const share_ptr<T, delfunc>& shptr)
:_ptr(shptr.getptr())
{}
weak_ptr<T, delfunc>& operator=(const weak_ptr<T, delfunc>& wptr)
{
_ptr = wptr._ptr;
return (*this);
}
weak_ptr<T, delfunc>& operator=(const share_ptr<T, delfunc>& shptr)
{
_ptr = shptr.getptr();
return (*this);
}
//让智能指针可以像指针一样被使用
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
private:
T* _ptr;
};
- 在自引用结构体中采用
weak_ptr
就可以避免出现循环引用的问题