上篇文章我们主要了解实现了auto_ptr 和 scoped_ptr,这两个实现总的来说都存在缺陷,所以在应用中使用起来不能得心应手,就有了接下来的升级版 shared_ptr,他是在scoped和auto_ptr的基础上利用了引用计数的方法解决了不同指针指向同块空间的问题,但完美的代码总是经过反复修改的代码,也存在缺陷,我们先看代码,后边解决。
#include<iostream>
using namespace std;
template<class T>
class Shared_Ptr
{
public:
shared_ptr(T* ptr) //构造
:_ptr(ptr)
, _refcount(new int(1))
{}
Shared_Ptr() //构造
:_ptr(NULL), _refcount(new int(1))
{}
Shared_Ptr(const sharedptr<T>& sp) //拷贝构造
:_ptr(sp._ptr), _refcount(sp._refcount)
{
(*_refcount)++;
}
Shared_Ptr<T>& operator=(const Shared_Ptr<T>& sp) //赋值运算符的重载
{
if (_ptr != sp._ptr)
{
delete _ptr;
delete _refcount;
_ptr = sp._ptr;
_refcount = sp._refcount;
++(*_refcount);
}
return *this;
}
~Shared_Ptr() //析构
{
Realease();
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
T Getrefcount()
{
return *(_refcount);
}
inline void Realease()
{
if (--*_refcount == 0)
{
delete _refcount;
delete _ptr;
}
}
void Reset(T* ptr ,T* refcount)
{
if (_ptr != ptr)
{
delete _ptr;
delete _refcount;
}
_ptr = ptr;
_refcount = refcount;
}
public:
T* _ptr;
T* _refcount;
//T _refcount;//有缺陷
//int static _refcount;//有缺陷
};
void test()
{
Shared_Ptr<int> s1(new int(10));
//cout << *s1._refcount << endl;
cout << s1.Getrefcount() << endl;
Shared_Ptr<int> s2(s1);
//cout << *s2._refcount << endl;
cout << s2.Getrefcount() << endl;
Shared_Ptr<int> s3(new int(20));
s3 = s1;
//cout << *s3._refcount << endl;
cout << s3.Getrefcount() << endl;
}
int main()
{
test();
//*Shared_Ptr<int> sp; // 验证Reset
//sp.Reset(new int,new int(1));
//*sp = 10;
//cout << *sp << endl;
//sp.Reset(new int, new int(1));
//*sp = 20;
//cout << *sp << endl;
//sp.Reset();
system("pause");
return 0;
}
类中多了一个成员 refcount,而且把他的类型定为了 T* 类型,这是为什么呢?当两个类成员相互拷贝的时候要修改引用计数,而且是多个类共同修改同一个引用计数,所以就要求共享这个类成员引用计数,那么就有人会想到把它设置为全局的静态,但是还有一个问题假如有多次赋值呢,引用计数只有到0时才会释放,这样看来还是不行,所以就只有把refcount的类型设计成T*类型,让这个模板指针指向一块空间,而这块空间存有引用计数的值。
接下来我们先看一段代码
#include<memory>
struct ListNode
{
shared_str<ListNode> _next;
shared_str<ListNode> _prev;
int _data;
ListNode(int x)
:_data(x)
, _next(NULL)
, _prev(NULL)
{}
~ListNode()
{
cout << "~ListNode()" << endl;
}
};
void test()
{
shared_str<ListNode> ap1(new ListNode(10));
shared_str<ListNode> ap2(new ListNode(20));
ap1->_next = ap2;
ap2->_prev = ap1;
}
int main()
{
test();
system("pause");
return 0;
}
这段代码有什么问题呢?当我们ap1和ap2相互赋值后编译器就没有调用析构函数了,这是为什么呢?
赋值之前他们的引用计数都是1,但是相互赋值之后都增加了变成了2,但是释放的时候ap2依赖于这个节点,而这个节点的释放依赖于前一个节点,ap1释放依赖于他所在的这个节点,而这个节点的适放依赖于他的后一个节点,这个就像一个环,谁也没法释放,这就是share_ptr存在的缺陷--循环引用,怎样解决这个问题呢,还得引出weak_ptr(弱指针),weak_ ptr是一种不控制所指向对象的智能指针,它指向由一个shared_ptr管理的对象。没有实现->和*运算符的重载,所以不能直接用它访问对象。将一个weak_ptr绑定到一个shared_ptr 不会改变shared_ptr的引用计数。
我们来看看利用弱指针改进了之后的shared_ptr
#include<iostream>
using namespace std;
#include<memory>
struct ListNode
{
weak_ptr<ListNode> _next;
weak_ptr<ListNode> _prev;
int _data;
ListNode(int x)
:_data(x)
{}
~ListNode()
{
cout << "~ListNode()" << endl;
}
};
void test()
{
shared_ptr<ListNode> ap1(new ListNode(10));
shared_ptr<ListNode> ap2(new ListNode(20));
ap1->_next = ap2;
ap2->_prev = ap1;
}
int main()
{
test();
system("pause");
return 0;
}
这样shared_ptr就是一个比较完善的一个智能指针,他通过指针保持某个对象的共享拥有权的智能指针。若干个shared_ptr对象可以拥有同一个对象,该对象通过维护一个引用计数,记录有多少个shared_ptr指针指向该对象,最后一个指向该对象的shared_ptr被销毁或重置时,即引用计数变为0时,该对象被销毁。销毁对象时使用的是delete表达式或是在构造shared_ptr时传入的自定义删除器(delete)。否则上面的指针不能用于文件的关闭,也不能用于管理malloc和new[]开辟的动态内存的释放,所以我们可以运用仿函数来定制删除器。
template<class T>
struct DeleteArray //用于new[]开辟的动态内存释放
{
void operator()(T* ptr)
{
cout << "A" << endl;
delete[] ptr;
}
};
struct Fclose //用于文件关闭
{
void operator()(FILE* ptr)
{
cout << "B" << endl;
fclose(ptr);
}
};
template<class T>
struct Free //用于malloc开辟的动态内存的释放
{
void operator()(T* ptr)
{
cout << "C" << endl;
free(ptr);
}
};
int main()
{
shared_ptr<string> ap1(new string[10], DeleteArray<string>());
shared_ptr<FILE> ap2(fopen("test.txt", "w"),Fclose());
shared_ptr<int> ap3((int*)malloc(sizeof(int)), Free<int>());
return 0;
}