2.3 模拟实现
思路:①当一块资源刚被开辟出来的时候,只有一个智能指针在管理,这个时候引用计数为1;
②每多来一个智能指针共同管理块这块资源的时候,这块资源的引用计数+1;
③每当某一个智能指针去管理其它资源或者直接走了啥也不管的时候,这块资源的引用计数需要-1并且当这块资源的引用计数减到0的时候就要被释放掉;
图解:
④每一块资源都应该有一个唯一的引用计数,不同资源间的引用计数互不影响,只有某一个智能指针去管理别的资源或者离开了引用计数才会-1,所以每一个智能指针它在管理一块资源的时候同时在管理这块资源的引用计数;
因此图可以画成这样更方便理解:
三、代码实现
#pragma once
namespace ldc//开辟一个命名空间,防止与库里的冲突
{
template<class T>
class shared_ptr
{
public:
//智能指针三个特性之一:RAII(利用对象的生命周期对资源的管控)
shared_ptr(T*p)
:_ptr(p),_pcout(new int(1))//刚开辟空间的时候只有一个智能指针在管理,所以引用计数为1
{
cout << "shared_ptr(T*p)" << endl;//调用构造的时候打印一下
}
~shared_ptr()
{
if (--(*_pcout)==0)//当这个智能指针去管理别的资源或者啥也不管直接走的时候把引用计数-1,当减到0的时候释放资源
{
cout << "delete" << endl;// 调用析构并释放资源的时候打印一下
delete _ptr;
}
}
//特性二:像指针一样
T& operator*()
{
return *_ptr;
}
T* operator->() {
return _ptr;
}
获取原生指针接口
//T* get()
//{
// return _ptr;
//}
//特性三:对拷贝跟赋值的处理方式
shared_ptr(const shared_ptr<T>& sp)
:_ptr(sp._ptr),_pcout(sp._pcout)
{
++(*_pcout);//每多一个智能指针共同管理,引用计数+1
}
shared_ptr<T>& operator=(const shared_ptr<T>& sp)
{
if (_ptr==sp._ptr)//如果管理同一块资源的智能指针之间相互赋值就直接返回
{
return *this;
}
if (--(*_pcout)==0)//指向别的资源前把自己当前管理的资源的引用计数--,当减到0释放资源
{
delete _ptr;
delete _pcout;
}
_ptr = sp._ptr;
_pcout = sp._pcout;
++(*_pcout);//每多一个智能指针共同管理,引用计数+1
return *this;
}
private:
T* _ptr;//原生指针
int* _pcout;//引用计数(每块资源的引用计数是唯一的,指向同一块资源同时指向这块资源的指针,用int*来记录个数合适)
};
}
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
using namespace std;
#include "shared_ptr.h"
class A
{
public:
A(int a = 0)
:_a(a)
{
cout << "A(int a = 0)" << endl;//调用构造就让它打印一下
}
~A()
{
cout << "~A()" << endl;//调用析构就让它打印一下
}
int _a;
};
int main()
{
ldc::shared_ptr<A> sp1(new A(1));
ldc::shared_ptr<A> sp2(new A(2));
ldc::shared_ptr<A> sp3(sp1);
sp2 = sp1;
return 0;
}
3.1运行:
四、引用循环
4.1shared_ptr在某些情况下会引发引用循环
例:当一个结点里有两个shared_ptr,一个指向前一个节点,一个指向后一个节点
struct Node
{
Node(int a=0)
:_a(a),_prev(nullptr),_next(nullptr)
{
cout << "Node(int a=0)" << endl ;
}
~Node()
{
cout << "~Node()" << endl ;
}
int _a;
ldc::shared_ptr<Node> _prev;
ldc::shared_ptr<Node> _next;
};
int main()
{
/*ldc::shared_ptr<A> sp1(new A(1));
ldc::shared_ptr<A> sp2(new A(2));
ldc::shared_ptr<A> sp3(sp1);
sp2 = sp1;*/
ldc::shared_ptr<Node> sp1(new Node(1));
ldc::shared_ptr<Node> sp2(new Node(2));
sp1->_next = sp2;
sp2->_prev = sp1;
return 0;
}
运行:
运行发现一次析构也没有调用到,也就是说一次资源也没有释放掉,造成了内存泄漏!!
为什么一次delete也没有调用到呢?这种场景就是引用循环,画图分析:
最后的结果:
那么节点1 什么时候释放? -> 节点2的 prev调析构节点1就释放;
那么节点2的prev什么时候调析构? -> 节点2释放,节点2 的prev就调析构;
那么节点2什么时候释放? -> 节点1的 next调析构节点2就释放;
那么节点1的next什么时候调析构? -> 节点1释放,节点1的next就调析构;
那么节点1什么时候释放?-> ..........
..........死循环。那么有什么办法可解决?
4.2 weak_ptr
weake_ptr不是RAII智能指针,是专门用来解决shared_ptr的循环引用的问题,weak_ptr不增加引用计数,不参与资源释放的管理,但可以访问资源。
五、weak_ptr的使用与模拟实现
5.1 weak_ptr 的使用
针对上面的问题,只需要将节点里的指针换成weak_ptr即可
代码更改后运行:
运行结果:调用了两次Node的析构,成功释放资源!
5.2weak_ptr的模拟实现
库里实现了默认构造,支持用weak_ptr构造,支持用shared_ptr构造
代码:
//在ldc命名空间里增加
template<class T>
class weak_ptr
{
public:
//weak_ptr不能RAII,只是用来解决shared_ptr循环引用的问题
weak_ptr()
:_ptr(nullptr)
{
}
~weak_ptr()
{
}
weak_ptr(const shared_ptr<T> &sp)
:_ptr(sp.get())//类外私有成员不可访问,shared_ptr还需要实现一个提供原生指针的接口
{
}
//支持shared_ptr赋值给weak_ptr
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;
};
运行:
运行结果也正确了,调用了两次析构,成功释放掉Node1、Node2;