目录
一,关于智能指针
1.1 场景一
先看下面代码:
int div()
{
int a, b;
cin >> a >> b;
if (b == 0)
throw "除0错误";
return a / b;
}
void Func1()
{
// 1、如果p1这里new 抛异常会如何?
// 2、如果p2这里new 抛异常会如何?
// 3、如果div调用这里又会抛异常会如何?
int* p1 = new int;
int* p2 = new int;
cout << div() << endl;
delete p1;
delete p2;
cout << "释放资源" << endl;
}
void main()
{
try
{
Func1();
}
catch (const char* errmsg)
{
cout << errmsg << endl;
}
}
我们在指针Func1函数,申请空间并且调用除法函数,先回答代码注释的三个问题:
①如果在new p1的时候抛异常了,没有影响
②如果在new p2的时候抛异常了,会直接跳到main的catch去,导致后面的delete p2无法执行,造成内存泄漏
③如果div里面抛异常了,和上面一样,p1和p2都无法释放,内存泄漏
1.2 场景二
对于上面的问题,我们可以这样解决:
int div()
{
int a, b;
cin >> a >> b;
if (b == 0)
throw "除0错误";
return a / b;
}
void Func1()
{
int* p1 = new int;
int* p2 = nullptr;
try
{
p2 = new int;
}
catch (...)
{
delete p1;
throw;
}
cout << div() << endl;
delete p1;
delete p2;
cout << "释放p1和p2" << endl;
}
void main()
{
try
{
Func1();
}
catch (const char* errmsg)
{
cout << errmsg << endl;
}
}
如上代码,如果new p2时抛异常,我们可以在Func1中先捕获异常,delete p1,然后再抛异常给main,但是,如果是div抛异常,不论怎样,div都会从throw的位置跳到main的捕获,无法释放p1和p2,而且p1和p2在Func1的函数栈帧里,所以也无法在main和div里调用delete释放,造成内存泄漏。
1.3 引出智能指针
上面的问题传统处理方式是很棘手的,所以C++11中推出了智能指针的概念,具体看下列代码和注释
template<class T>
class SmartPtr
{
public:
SmartPtr(T* ptr = nullptr)
:_ptr(ptr)
{}
~SmartPtr()
{
if (_ptr)
{
cout << "Delete:" << _ptr << endl;
delete _ptr;
}
}
//智能指针是指针,所以可以像指针一样解引用和->
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
private:
T* _ptr;
};
void Func2()
{
SmartPtr<int> sp1(new int);//把申请的资源交给适sp1,通过sp1的释放来释放p1
SmartPtr<int> sp2(new int);
//不论是Func正常结束还是抛异常,sp1和sp2都会调用析构函数释放资源
cout << div() << endl;
}
class A
{
public:
~A()
{
cout << "~A()" << endl;
}
int _a1 = 0;
int _a2 = 0;
};
void main1()
{
SmartPtr<A> ap1(new A);//A自己调用析构函数
ap1->_a1++;
ap1->_a2++;
//智能指针的拷贝问题
//SmartPtr<A> ap2(ap1);
//拷贝时直接报错,浅拷贝,同一块地方释放两次,而且不能深拷贝,因为违背了功能需求
}
如上面代码,我们把new出来的对象通过智能指针类的构造函数交给类管理,这样,我们new的资源就会随着智能指针类的构造函数初始化而初始化,随类的析构而释放,可以解决场景一二的问题。
这种通过类来管理资源的方式叫做RAII
void main()
{
SmartPtr<pair<string, int>> sp1(new pair<string, int>("sort", 1));
sp1->second++;
cout << sp1->first << ":" << sp1->second << endl;
}
1.4 RAII
RAII是一种利用声明周期来控制程序资源的简单技术。
在对象构造时获取资源,接着控制对资源的访问使之在生命周期内始终保持有效,最后在对象析构时释放资源。借此,我们实际上把管理一份资源的责任托管给了一个对象,这种做法有两大好处:
①不需要显示地释放资源
②采用这种方式,对象所需地资源在其生命周期内始终保持有效
1.5 智能指针拷贝
就上面地SmartPtr来说,如果我们SmartPtr<A> ap2(ap1)时,看似没啥问题,但是最后函数结束地时候必定报错.本来是ap1管资源,现在拷贝后ap1和ap2一起管了,最后析构时会对这块资源析构两次,典型的浅拷贝问题。而且不能深拷贝,因为违背了功能需求,我们只是需要管理这块资源,并不需要额外空间
所以,为了解决这种问题,C++11给出了多种智能指针类型,对拷贝问题给出了多种解决方案。
问题:链表/红黑树等迭代器跟智能指针结构类型,但是迭代器用的是浅拷贝,为啥没问题?
因为迭代器不管资源释放,它只管访问资源,修改资源,因为释放资源的事情是交给析构函数的。而指针指针需要资源释放,所以不能单纯地浅拷贝
二,智能指针类型
class A
{
public:
~A()
{
cout << "~A()" << endl;
}
int _a1 = 0;
int _a2 = 0;
};
2.1 auto_ptr
C++98的库中就提供了auto_ptr的智能指针,但是它的拷贝是一种极其“不负责任”的拷贝,它只实现资源管理权限的转移,会导致对象被拷贝走后悬空,再次访问就会报错。(多年来被挂在耻辱柱上,很多公司明确要求不能使用它)
namespace bit
{
template<class T>
class auto_ptr
{
public:
auto_ptr(T* ptr = nullptr)
: _ptr(ptr)
{}
auto_ptr(auto_ptr<T>& ap)//模拟实现std中auto_ptr的拷贝功能
:_ptr(ap._ptr)
{
ap._ptr = nullptr;
}
// ap1 = ap2;
auto_ptr<T>& operator=(auto_ptr<T>& ap)
{
if (this != &ap)
{
if (_ptr)
{
cout << "Delete:" << _ptr << endl;
delete _ptr;
}
_ptr = ap._ptr;
ap._ptr = nullptr;
}
return *this;
}
~auto_ptr()
{
if (_ptr)
{
cout << "Delete:" << _ptr << endl;
delete _ptr;
}
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
private:
T* _ptr;
};
}
void test_auto_ptr()
{
bit::auto_ptr<A> ap1(new A);
ap1->_a1++;
ap1->_a2++;
//std::auto_ptr<A> ap2(ap1);程序直接挂了
bit::auto_ptr<A> ap2(ap1);//拷贝,管理权转移,直接把ap1变成nullptr了
//*ap1; 管理权转移后大致ap1悬空,这时候对ap1进行任何解引用操作会直接报错
ap2->_a1++;
ap2->_a2++;
// 2 2
cout << ap2->_a1 << endl;
cout << ap2->_a2 << endl;
bit::auto_ptr<A> ap3(new A);
ap2 = ap3;//管理权转移,有一次析构
ap2->_a1++;
ap2->_a2++;
// 1 1
cout << ap2->_a1 << endl;
cout << ap2->_a2 << endl;
}
2.2 unique_ptr
unique_ptr非常简单粗暴,它直接把拷贝和赋值给禁掉了,直接从根源上解决问题
namespace bit
{
template<class T>
class unique_ptr
{
private:
// 防拷贝 C++98的解决方法
// 只声明成私有,并且不实现,私有是因为可能会在类外面实现
//unique_ptr(unique_ptr<T>& ap);
//unique_ptr<T>& operator=(unique_ptr<T>& ap);
public:
unique_ptr(T* ptr = nullptr)
: _ptr(ptr)
{}
// 防拷贝 C++11的解决方法
// 直接把拷贝和赋值禁掉
unique_ptr(unique_ptr<T>& ap) = delete;
unique_ptr<T>& operator=(unique_ptr<T>& ap) = delete;
~unique_ptr()
{
if (_ptr)
{
cout << "Delete:" << _ptr << endl;
delete _ptr;
}
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
private:
T* _ptr;
};
}
//不需要拷贝的场景就用这个,但是我们还是有需要拷贝的场景的
void test_unique_ptr()
{
bit::unique_ptr<A> up1(new A);
//std::unique_ptr<A> up2(up2);//直接报错,不让你拷贝
up1->_a1++;
up1->_a2++;
bit::unique_ptr<A> up3(new A);
//up1 = up2;
}
2.3 shared_ptr
unique_ptr已经能解决大部分问题了,但是有些场景下我们还是需要拷贝的,所以我们推出了shared_ptr。它采用对资源计数的方法控制资源的释放
namespace bit
{
//一个资源,配一个计数,资源由多个指针对象共同管理
template<class T>
class shared_ptr
{
public:
shared_ptr(T* ptr = nullptr)
: _ptr(ptr)
, _pCount(new int(1))
{}
void Release()
{
if (--(*_pCount) == 0)
{
if (ptr)
{
cout << "delete:" << ptr << endl;
delete ptr;
}
delete _pCount;
}
}
void AddCount()
{
++(*_pCount);
}
~shared_ptr()
{
Release();
}
//sp1(sp2)
shared_ptr(shared_ptr<T>& sp)
: _ptr(sp._ptr)
, _pCount(sp._pCount)
{
AddCount();
}
shared_ptr<T>& operator=(const shared_ptr<T>& sp)
{
//if(_ptr = &sp) 这种只能防sp1 = sp1, 如果是是sp1 = sp5后再次sp5 = sp1,就用下面这个
if (_ptr == sp._ptr)
return *this;
//赋值时如果计数为一,则代表sp1是目前管理这块空间的唯一者,现在这个唯一者要被赋值替换走,那么释放这块空间
Release();
//sp1 = sp2
_ptr = sp._ptr; //将指向sp2资源的指针覆盖sp1的指针
_pCount = sp._pCount; //sp2的计数也改一下
AddCount(); //计数++,上面的Release已经释放了sp1的空间
return *this;
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
int use_count()
{
return *_pCount;
}
T* get() const
{
return _ptr;
}
private:
T* _ptr;
int* _pCount;//引用计数
//这个计数要求是公共的,但是不能定义成static,因为如果是静态的,就属于这个类的所有对象
//假设sp1,sp2,sp3指向一个空间,计数为3,但是如果sp4重新开了一块空间,那么计数就变成了1,这是个大问题
//多个对象可能多个资源,所以应该是每个资源配对一个引用计数,不是对象
};
}
// shared_ptr 如果就是需要拷贝的场景
void test_shared_ptr()
{
bit::shared_ptr<A> sp1(new A);
bit::shared_ptr<A> sp2(sp1);
bit::shared_ptr<A> sp3(sp2);
sp1->_a1++;
sp1->_a2++;
cout << sp2->_a1 << ":" << sp2->_a2 << endl;
sp2->_a1++;
sp2->_a2++;
cout << sp1->_a1 << ":" << sp1->_a2 << endl;
bit::shared_ptr<A> sp5(new A);
bit::shared_ptr<A> sp6(sp5);
sp1 = sp5;
sp2 = sp5;
sp3 = sp5;
// 自己给自己赋值
bit::shared_ptr<int> sp4(new int);
sp4 = sp4;
sp1 = sp5;
}
2.4 shared_ptr的线程安全问题
一智能指针对象中引用计数是多个线程对象共享的,所以多个线程可以同时对计数++或--,多以这个操作不是原子的,不是线程安全的。所以在对计数++或--的时候需要加锁和释放锁
所以我们来改造shared_ptr:
namespace bit
{
template<class T>
class shared_ptr
{
public:
shared_ptr(T* ptr = nullptr)
:_ptr(ptr)
, _pCount(new int(1))
, _pmtx(new mutex)
{}
void Release()
{
_pmtx->lock();
bool deleteFlag = false;
if (--(*_pCount) == 0)
{
cout << "Delete:" << _ptr << endl;
delete _ptr;
delete _pCount;
deleteFlag = true; //用一个bool来控制锁的释放
}
_pmtx->unlock();
if (deleteFlag)
{
delete _pmtx;
}
}
void AddCount()
{
_pmtx->lock();
++(*_pCount);
_pmtx->unlock();
}
~shared_ptr()
{
Release();
}
//sp1(sp2)
shared_ptr(shared_ptr<T>& sp)
: _ptr(sp._ptr)
, _pCount(sp._pCount)
, _pmtx(sp._pmtx)
{
AddCount();
}
//sp1 = sp5
//sp1 = sp1
shared_ptr<T>& operator=(const shared_ptr<T>& sp)
{
//if(_ptr = &sp) 这种只能防sp1 = sp1, 如果是是sp1 = sp5后再次sp5 = sp1,就用下面这个
if (_ptr == sp._ptr)
return *this;
//赋值时如果计数为一,则代表sp1是目前管理这块空间的唯一者,现在这个唯一者要被赋值替换走,那么释放这块空间
Release();
//sp1 = sp2
_ptr = sp._ptr; //将指向sp2资源的指针覆盖sp1的指针
_pCount = sp._pCount; //sp2的计数也改一下
_pmtx = sp._pmtx;
AddCount(); //计数++,上面的Release已经释放了sp1的空间
return *this;
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
int use_count()
{
return *_pCount;
}
T* get() const
{
return _ptr;
}
private:
T* _ptr;
int* _pCount;//引用计数
//这个计数要求是公共的,但是不能定义成static,因为如果是静态的,就属于这个类的所有对象
//假设sp1,sp2,sp3指向一个空间,计数为3,但是如果sp4重新开了一块空间,那么计数就变成了1,这是个大问题
//多个对象可能多个资源,所以应该是每个资源配对一个引用计数,不是对象
mutex* _pmtx;
};
}
struct Date
{
int _year = 0;
int _month = 0;
int _day = 0;
~Date()
{}
};
//shared_ptr本身是线程安全的,因为计数是加锁保护的
void SharePtrFunc(bit::shared_ptr<Date>& sp, size_t n, mutex& mtx)
{
//cout << &sp << endl; 虽然这里给的是引用,但是下面的p没有直接传给sp,sp接收的是p的拷贝,所以要加ref
cout << sp.get() << endl;
for (size_t i = 0; i < n; ++i)
{
// 这里智能指针拷贝会++计数,智能指针析构会--计数,这里是线程安全的。
bit::shared_ptr<Date> copy(sp);
// 这里智能指针访问管理的资源,不是线程安全的。所以我们看看这些值两个线程++了2n次,但是最终看到的结果,不一定是加了2n
mtx.lock();
sp->_year++;
sp->_month++;
sp->_day++;
mtx.unlock();
}
}
void test_shared_ThreadSafe()
{
bit::shared_ptr<Date> p(new Date);
cout << p.get() << endl;
const size_t n = 50000;
mutex mtx;
thread t1(SharePtrFunc, std::ref(p), n, std::ref(mtx)); //规定想在线程的参数里传引用需要这样传
thread t2(SharePtrFunc, std::ref(p), n, std::ref(mtx)); //
t1.join();
t2.join();
cout << p.use_count() << endl;
cout << p->_year << endl;
cout << p->_month << endl;
cout << p->_day << endl;
}
2.5 循环引用和weak_ptr
struct Node
{
int _val;
//n1->_next = n2; 无法赋值,因为n1是智能指针,_next是原生指针,所以Node里面也要搞成智能指针
bit::shared_ptr<Node> _next;
bit::shared_ptr<Node> _prev;
~Node()
{
cout << "~Node()" << endl;
}
};
void test_shared_cycle()
{
bit::shared_ptr<Node> n1(new Node);//无法n1 = new Node,不支持隐式类型转换
bit::shared_ptr<Node> n2(new Node);
cout << n1.use_count() << endl;
cout << n2.use_count() << endl;
n1->_next = n2;
n2->_prev = n1;
//不增加引用计数
cout << n1.use_count() << endl;
cout << n2.use_count() << endl;
}
观察上面代码,我们用智能指针作为链表的的前后指针类型,然后我们定义两个节点,让它们的前后都指向自己,这时候,我们发现,当函数结束后,不打印Node的析构函数,这就代表着未执行节点的析构函数,节点资源未释放。为什么呢?看下图
这是我们双链表的基本结构,两个节点,里面都有两个指针,类型为shared_ptr,当函数结束后要释放资源,首先释放n1,_val被释放,然后shared_ptr调用自己的析构函数,本来没啥问题,但是释放_next时,除了释放_next自己的资源时,还会去释放它指向的资源,它指向的是n2,所以再释放n2,然后释放n2时,_prev和_next都调用自己的析构,然后再去释放它们指向的资源,然后它们指向n1。
所以这样就形成了一种死循环,n1啥时候释放取决于n2,n2啥时候释放取决于n1,冤家路窄,狭路相逢,谁也不放过谁,就无法释放资源了,造成内存泄漏。这种情况我们叫做循环引用
所以我们用weak_ptr来解决循环引用,它是一个辅助型智能指针,是专门设计出来的解决shared_ptr的循环引用问题的。它不是常规的智能指针,所以不支持RAII,weak_ptr可以指向资源,但是不参与管理,也就是不对引用计数进行++或--
//shared_ptr的循环引用问题
//辅助型智能指针,配合解决shared_ptr循环引用问题
//它不是常规的智能指针,不支持RAII
//专门设计出来,辅助解决shared_ptr的循环引用问题
//weak_ptr可以指向资源,但是不参与管理(不增加引用计数)
namespace bit
{
template<class T>
class weak_ptr //模拟实现极简版本,C++标准库的实现非常复杂
{
public:
weak_ptr()
:_ptr(nullptr)
{}
weak_ptr(const shared_ptr<T>& sp)
:_ptr(sp.get())
{}
weak_ptr(const weak_ptr<T>& wp)
:_ptr(wp._ptr)
{}
weak_ptr<T>& operator=(const shared_ptr<T>& sp)
{
_ptr = sp.get();
return *this;
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
public:
T* _ptr;
};
}
struct Node
{
int _val;
//n1->_next = n2; 无法赋值,因为n1是智能指针,_next是原生指针,所以Node里面也要搞成智能指针
/*bit::shared_ptr<Node> _next;
bit::shared_ptr<Node> _prev;*/
bit::weak_ptr<Node> _next;
bit::weak_ptr<Node> _prev;
~Node()
{
cout << "~Node()" << endl;
}
};
void test_shared_cycle()
{
bit::shared_ptr<Node> n1(new Node);//无法n1 = new Node,不支持隐式类型转换
bit::shared_ptr<Node> n2(new Node);
cout << n1.use_count() << endl;
cout << n2.use_count() << endl;
n1->_next = n2;
n2->_prev = n1;
//节点被delete时,由于_next和_prev都是智能指针会调用自己的析构函数,n1的_next和n2的_prev作为成员会析构
//n1里面有n2,n2里面有n1,简单来说n1啥时候释放取决于n2,n2啥时候释放取决于n1,谁也不放过谁,冤家路窄,狭路相逢,就无法释放,造成内存泄露
//所以用weak_ptr解决
//不增加引用计数
cout << n1.use_count() << endl;
cout << n2.use_count() << endl;
}
三,定制删除器(了解)
3.1 场景
template<class T>
struct DeleteArray
{
void operator()(T* ptr)
{
cout << "delete[]" << ptr << endl;
delete[] ptr;
}
};
template<class T>
struct Free
{
void operator()(T* ptr)
{
cout << "free" << ptr << endl;
free(ptr);
}
};
//定制删除器 -- 其实就是仿函数,可调用对象
void test_shared_deletor()
{
//指针偏移
//std::shared_ptr<Node> n1(new Node[5]); //Date如果我们写了析构会崩,因为new[]时会多开四个字节表示需要调几次析构函数,不写析构函数时不会多开四个字节,那么delete的位置就是对的,但是如果我们写了,那么释放的位置就不对了
std::shared_ptr<int> n2(new int[5]); //没崩
std::shared_ptr<Node> n3(new Node); //没崩
std::shared_ptr<int> n4((int*)malloc(sizeof(12)));
//仿函数
std::shared_ptr<Node> n5(new Node[5], DeleteArray<Node>());
std::shared_ptr<int> n7(new int[5], DeleteArray<int>());
std::shared_ptr<Node> n6(new Node);
std::shared_ptr<int> n8((int*)malloc(sizeof(12)), Free<int>());
//这里用lambda就非常爽
std::shared_ptr<Node> n9(new Node[5], [](Node* ptr){delete[] ptr; });
std::shared_ptr<int> n10(new int[5], [](int* ptr) {delete[] ptr; });
std::shared_ptr<Node> n11(new Node);
std::shared_ptr<int> n12((int*)malloc(sizeof(12)), [](int* ptr){free(ptr); });
std::shared_ptr<FILE> n13(fopen("test.txt", "w"), [](FILE* ptr){fclose(ptr); });
std::unique_ptr<Node, DeleteArray<Node>> up(new Node[5]);
}
如同上面的注释部分,如果我们使用[]一下子申请好几个Node,如果我们的Node写了析构函数,到时候开空间的时候会多开一个int用来记录申请的Node个数方便最后调用对应数量的析构函数, 但是最后delete会直接报错
所以针对这种情况,shared_ptr设计了定制删除器来解决这个问题
3.2 使用包装器解决
namespace bit
{
//一个资源,配一个计数,资源由多个指针对象共同管理
template<class T>
class shared_ptr1
{
public:
shared_ptr1(T* ptr = nullptr)
:_ptr(ptr)
, _pCount(new int(1))
, _pmtx(new mutex)
{}
template<class D>
shared_ptr1(T* ptr, D del)
: _ptr(ptr)
, _pCount(new int(1))
, _pmtx(new mutex)
, _del(del)
{}
void Release()
{
_pmtx->lock();
bool deleteFlag = false;
if (--(*_pCount) == 0)
{
if (_ptr)
{
//cout << "delete:" << _ptr << endl;
//delete _ptr;
_del(_ptr);
}
}
_pmtx->unlock();
if (deleteFlag)
{
delete _pmtx;
}
}
void AddCount()
{
_pmtx->lock();
++(*_pCount);
_pmtx->unlock();
}
~shared_ptr2()
{
Release();
}
//sp1(sp2)
shared_ptr2(shared_ptr<T>& sp)
: _ptr(sp._ptr)
, _pCount(sp._pCount)
, _pmtx(sp._pmtx)
{
AddCount();
}
shared_ptr2<T>& operator=(const shared_ptr2<T>& sp)
{
if (_ptr == sp._ptr)
return *this;
Release();
_ptr = sp._ptr;
_pCount = sp._pCount;
_pmtx = sp._pmtx;
AddCount();
return *this;
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
int use_count()
{
return *_pCount;
}
T* get() const
{
return _ptr;
}
private:
T* _ptr;
int* _pCount;
mutex* _pmtx;
//包装器
function<void(T*)> _del = [](T* ptr)
{
cout << "lamdba delete:" << ptr << endl;
delete ptr;
};
};
}
void test_shared_ptr1()
{
bit::shared_ptr1<Date> sp0(new Date);
bit::shared_ptr1<Date> spa1(new Date[10], DeleteArray<Date>());
bit::shared_ptr1<Date> spa2(new Date[10], [](Date* ptr) {
cout << "lambda delete[]:" << ptr << endl;
delete[] ptr;
});
bit::shared_ptr1<FILE> spF3(fopen("Test.cpp", "r"), [](FILE* ptr) {
cout << "lambda fclose:" << ptr << endl;
fclose(ptr);
});
}
3.3 使用仿函数解决
template<class T, class D = Delete<T>>
class shared_ptr2
{
public:
shared_ptr2(T* ptr = nullptr)
:_ptr(ptr)
, _pCount(new int(1))
, _pmtx(new mutex)
{}
void Release()
{
_pmtx->lock();
bool deleteFlag = false;
if (--(*_pCount) == 0)
{
//cout << "Delete:" << _ptr << endl;
// delete _ptr;
//D del;
//del(_ptr)
D()(_ptr);
delete _pCount;
deleteFlag = true;
}
_pmtx->unlock();
if (deleteFlag)
{
delete _pmtx;
}
}
void AddCount()
{
_pmtx->lock();
++(*_pCount);
_pmtx->unlock();
}
~shared_ptr()
{
Release();
}
//sp1(sp2)
shared_ptr(shared_ptr<T>& sp)
: _ptr(sp._ptr)
, _pCount(sp._pCount)
, _pmtx(sp._pmtx)
{
AddCount();
}
//sp1 = sp5
//sp1 = sp1
shared_ptr<T>& operator=(const shared_ptr<T>& sp)
{
//if(_ptr = &sp) 这种只能防sp1 = sp1, 如果是是sp1 = sp5后再次sp5 = sp1,就用下面这个
if (_ptr == sp._ptr)
return *this;
//赋值时如果计数为一,则代表sp1是目前管理这块空间的唯一者,现在这个唯一者要被赋值替换走,那么释放这块空间
Release();
//sp1 = sp2
_ptr = sp._ptr; //将指向sp2资源的指针覆盖sp1的指针
_pCount = sp._pCount; //sp2的计数也改一下
_pmtx = sp._pmtx;
AddCount(); //计数++,上面的Release已经释放了sp1的空间
return *this;
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
int use_count()
{
return *_pCount;
}
T* get() const
{
return _ptr;
}
private:
T* _ptr;
int* _pCount;//引用计数
//这个计数要求是公共的,但是不能定义成static,因为如果是静态的,就属于这个类的所有对象
//假设sp1,sp2,sp3指向一个空间,计数为3,但是如果sp4重新开了一块空间,那么计数就变成了1,这是个大问题
//多个对象可能多个资源,所以应该是每个资源配对一个引用计数,不是对象
mutex* _pmtx;
};
}
void test_shared_ptr2()
{
bit::shared_ptr2<Date> sp0(new Date);
bit::shared_ptr2<Date> spa1(new Date[10], DeleteArray<Date>());
bit::shared_ptr2<Date> spa2(new Date[10], [](Date* ptr) {
cout << "lambda delete[]:" << ptr << endl;
delete[] ptr;
});
bit::shared_ptr2<FILE> spF3(fopen("Test.cpp", "r"), [](FILE* ptr) {
cout << "lambda fclose:" << ptr << endl;
fclose(ptr);
});
}
最后这个shared_ptr建议多看十多次,最好能手撕,因为面试时,经常会让你手撕