●🧑个人主页:你帅你先说.
●📃欢迎点赞👍关注💡收藏💖
●📖既选择了远方,便只顾风雨兼程。
●🤟欢迎大家有问题随时私信我!
●🧐版权:本文由[你帅你先说.]原创,CSDN首发,侵权必究。
#include
#include
#include
using namespace std;
int* p1 = new int;
int* p2 = nullptr, * p3 = nullptr;
int div()
{
int a = 3, b = 0;
if (b == 0)
throw invalid_argument("除0错误");
//若此时抛出了异常, 那下面这段代码就没法执行了,就会导致内存泄漏
delete p1;
delete p2;
delete p3;
return a / b;
}
void func()
{
try
{
p2 = new int;
p3 = new int;
cout << div() << endl;
}
catch (...)
{
}
}
int main()
{
func();
}
智能指针用来解决内存泄漏
和异常安全问题
。
RAII(Resource Acquisition Is Initialization)是
一种利用对象生命周期来控制程序资源
(如内存、文件句柄、网络连接、互斥量等等)的简单技术。
在对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终保持有效,最后在对象析构的时候释放资源。借此,我们实际上把管理一份资源的责任托管给了一个对象。这种做法有两大好处:
- 不需要显式地释放资源。
- 采用这种方式,对象所需的资源在其生命期内始终保持有效。
使用RAII思想设计智能指针
template<class T>
class SmartPtr
{
public:
//构造函数
SmartPtr(T* ptr):_ptr(ptr)
{}
//析构函数
~SmartPtr()
{
cout << "delete:" << _ptr << endl;
delete _ptr;
}
//重载*运算符
T& operator*()
{
return *_ptr;
}
//重载->运算符
T* operator->()
{
return _ptr;
}
private:
T* _ptr;
};
智能指针的原理:
1.RAII特性
2.重载operator*和opertaor->,具有像指针一样的行为。
实现原理:管理权转移。
我们直接来看模拟实现
template<class T>
class auto_ptr
{
public:
//构造函数
auto_ptr(T* ptr):_ptr(ptr)
{}
//拷贝构造函数
auto_ptr(auto_ptr<T>& sp)
:_ptr(sp._ptr)
{
// 管理权转移
sp._ptr = nullptr;
}
//析构函数
~auto_ptr()
{
if (_ptr)
{
cout << "delete:" << _ptr << endl;
delete _ptr;
}
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
private:
T* _ptr;
};
但实际上auto_ptr在实际中很少使用,因为它的设计有缺陷。
实现原理:防拷贝。
template<class T>
class unique_ptr
{
public:
//构造函数
unique_ptr(T* ptr):_ptr(ptr)
{}
//析构函数
~unique_ptr()
{
if (_ptr)
{
cout << "delete:" << _ptr << endl;
delete _ptr;
}
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
//防止拷贝
unique_ptr(const unique_ptr<T>& sp) = delete;
private:
T* _ptr;
};
但即便这样设计还是有问题,万一在某种场景下我需要拷贝。所以就有了接下来的shared_ptr。
实现原理:通过引用计数的方式来实现多个shared_ptr对象之间共享资源。
template<class T>
class shared_ptr
{
public:
shared_ptr(T* ptr = nullptr):_ptr(ptr), _pRefCount(new int(1)), _pmtx(new mutex)
{}
shared_ptr(const shared_ptr<T>& sp):_ptr(sp._ptr), _pRefCount(sp._pRefCount), _pmtx(sp._pmtx)
{
AddRef();
}
void Release()
{
//加锁保证线程安全
_pmtx->lock();
bool flag = false;
if (--(*_pRefCount) == 0 && _ptr)
{
cout << "delete:" << _ptr << endl;
delete _ptr;
delete _pRefCount;
flag = true;
}
_pmtx->unlock();
if (flag == true)
{
delete _pmtx;
}
}
void AddRef()
{
_pmtx->lock();
++(*_pRefCount);
_pmtx->unlock();
}
shared_ptr<T>& operator=(const shared_ptr<T>& sp)
{
//if (this != &sp)
//这里不能写成this != &sp,因为可能会出现指向同一个对象的指针相互赋值,这样就没有意义了,下面这样写可以提高效率。
if (_ptr != sp._ptr)
{
Release();
_ptr = sp._ptr;
_pRefCount = sp._pRefCount;
_pmtx = sp._pmtx;
AddRef();
}
return *this;
}
int use_count()
{
return *_pRefCount;
}
~shared_ptr()
{
Release();
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
T* get() const
{
return _ptr;
}
private:
T* _ptr;
int* _pRefCount;
mutex* _pmtx;
};
几点注意:
这种智能指针是用来处理一种特殊的场景:循环引用。
先来看看模拟实现。
template<class T>
class weak_ptr
{
public:
weak_ptr()
:_ptr(nullptr)
{}
weak_ptr(const shared_ptr<T>& sp)
:_ptr(sp.get())
{}
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;
};
你会发现weak_ptr的设计相较于shared_ptr其实就是取消了引用计数。
循环引用场景:
struct ListNode
{
int _val;
std::shared_ptr<ListNode> _next;
std::shared_ptr<ListNode> _prev;
~ListNode()
{
cout << "~ListNode()" << endl;
}
};
int main()
{
std::shared_ptr<ListNode> n1(new ListNode);
std::shared_ptr<ListNode> n2(new ListNode);
// 循环引用
n1->_next = n2;
n2->_prev = n1;
return 0;
}
这段代码你可能没看出来什么问题,一张图带你了解。
解决方案:把结构体内的shared_ptr换成weak_ptr就可以解决了。
struct ListNode
{
int _val;
std::shared_ptr<ListNode> _next;
std::shared_ptr<ListNode> _prev;
~ListNode()
{
cout << "~ListNode()" << endl;
}
};
int main()
{
std::shared_ptr<ListNode> n1(new ListNode);
std::shared_ptr<ListNode> n2(new ListNode);
// 循环引用
n1->_next = n2;
n2->_prev = n1;
return 0;
}
喜欢这篇文章的可以给个一键三连
点赞👍关注💡收藏💖