智能指针shared_ptr
实现一个简单的shared_ptr类
#include<iostream>
#include<mutex>
//仿函数
template<class T>
class DFDel
{
public:
void operator()(T*& p)
{
if (p)
{
delete p;
p = nullptr;
}
}
};
template<class T>
class Free
{
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 smart_ptr
{
template<class T, class DF = DFDel<T>> //第二个参数使用仿函数
class shared_ptr
{
public:
shared_ptr(T* ptr = nullptr)
:_ptr(ptr)
, _pMutex(new std::mutex())
{
if (_ptr)
_pCount = new int(1);
}
shared_ptr(const shared_ptr<T>& sp)
:_ptr(sp._ptr)
, _pCount(sp._pCount)
, _pMutex(new std::mutex())
{
AddRefCount();
}
//在和其他对象共享时,需要将自身对象的引用计数进行修改
shared_ptr<T>& operator=(const shared_ptr<T>& sp)
{
if (_ptr != sp._ptr)
{
Release();//在判断的过程中也将引用计数进行了--操作
_ptr = sp._ptr;
_pCount = sp._pCount;
AddRefCount();
}
return *this;
}
T& operator*() const
{
return *_ptr;
}
T* operator->() const
{
return _ptr;
}
~shared_ptr()
{
Release();
delete _pMutex;
}
private:
void Release()
{
_pMutex->lock();
if (_ptr && 0 == --(*_pCount))
{
//delete _ptr; 为了完成其他类型的空间释放
DF()(_ptr); // DF() 表示创建了一个无名对象,在使用无名对象调用()重载函数
delete _pCount;
}
_pMutex->unlock();
}
void AddRefCount()
{
_pMutex->lock();
if (_ptr)
(*_pCount)++;
_pMutex->unlock();
}
private:
T* _ptr;
int* _pCount;
std::mutex* _pMutex; //可以进行加锁,解锁,保证其变为线程安全的
};
void TestFunc1()
{
shared_ptr<int> sp1(new int);
shared_ptr<int> sp2(sp1);
shared_ptr<int> sp3;
shared_ptr<int> sp4(new int);
shared_ptr<int> sp5(new int);
sp3 = sp1;
sp4 = sp2;
sp1 = sp5;
}
void TestFunc2()
{
//shared_ptr<int> sp1(new int[10]);
//一段连续空间的话,可以直接使用vector来管理空间
shared_ptr<FILE, FClose> sp2(fopen("array_test.cpp", "rb"));
shared_ptr<int, Free<int>> sp3((int*)malloc(sizeof(int)));
//shared_ptr<int> sp;
}
}
仿函数的作用:实现不同类型的ptr指针的释放,比如说malloc申请出来的空间交予智能指针管理,或者FILE类型文件指针交予shared_ptr进行关闭理时,在进行释放时,调用不同的仿函数 malloc申请的调用free进行释放,FILE类型的调用fcolse进行释放,默认参数DFDel表示普通new类型的空间的释放该空间。
加锁的作用:对引用计数的增减操作都加上了锁,防止多线程环境下对引用计数的操作不当(也可以使用atomic关键字来对引用计数进行修饰)
———>后面的两个测试用例可供读者参考。
循环引用问题
循环引用具体描述:在使用shared_ptr(引用计数)的只能指针时,可能会用该只能指针对象来管理链表的结点,如果是双向链表等复杂链表,会出现下图情况👇
如上图所以,可以有如下代码进行验证👇
struct ListNode
{
ListNode(int data = int())
: pre(nullptr)
, next(nullptr)
, _data(data)
{
std::cout << "ListNode():" << this << std::endl;
}
~ListNode()
{
std::cout << "~ListNode" << std::endl;
}
//ListNode* _pPre;
//ListNode* _pNext;
//使用智能指针管理
std::shared_ptr<ListNode> pre;
std::shared_ptr<ListNode> next;
int _data;
};
int main()
{
std::shared_ptr<ListNode> sp1(new ListNode(10));
std::shared_ptr<ListNode> sp2(new ListNode(20));
std::cout << sp1.use_count() << std::endl;
std::cout << sp2.use_count() << std::endl;
sp1->next = sp2;
sp2->pre = sp1;
std::cout << sp1.use_count() << std::endl;
std::cout << sp2.use_count() << std::endl;
}
可以看到程序运行到最后并没有调用析构函数,所以已经导致了内存泄漏
解决方式
将结点内部的指针改用weak_ptr智能指针进行管理
修改之后的代码如下
struct ListNode
{
ListNode(int data = int())
: _data(data)
{
std::cout << "ListNode():" << this << std::endl;
}
~ListNode()
{
std::cout << "~ListNode" << this << std::endl;
}
//ListNode* _pPre;
//ListNode* _pNext;
std::weak_ptr<ListNode> pre; //使用weak_Ptr 控制结构体中的指针
std::weak_ptr<ListNode> next;
int _data;
};
int main()
{
std::shared_ptr<ListNode> sp1(new ListNode(10));
std::shared_ptr<ListNode> sp2(new ListNode(20));
std::cout << sp1.use_count() << std::endl;
std::cout << sp2.use_count() << std::endl;
sp1->next = sp2;
sp2->pre = sp1;
std::cout << sp1.use_count() << std::endl;
std::cout << sp2.use_count() << std::endl;
return 0;
}
图解
构造时:
第一步:构造只能指针对象,将Node结点存入,两个引用计数都+1
第二步:改变指针的指向,并且将ref_Weak的计数再+1
析构释放时:
第一步:先析构shared_ptr Node1,将其Uses进行-1操作,然后增加了自身Weak计数的-1操作然后调用Node的析构函数,将结点释放
第二步:在析构Node结点时,需要析构内部的空间,即,由于没有next的计数为0,所以释放next的空间,在进行释放per的空间,断开ptr的指针,然后再将其weak计数-1在完成后,代表着整个Node2释放完毕
第三步:释放节点Node1,将其Uses进行-1操作,然后增加了自身Weak计数的-1操作,发现自身Weak计数为0,所以将自身的weak引用计数空间释放然后调用Node的析构函数,将结点释放
第四步:在析构Node1结点时,需要析构内部的空间,即,在进行释放next的空间,断开ptr的指针,然后再将其weak计数-1,发现Node1的Weak计数为0,所以释放该weak空间,pre为空直接进行释放,在完成后,代表着整个Node1释放完毕