您的位置:首页 > 编程语言 > C语言/C++

智能指针(下)-----boost库智能指针,定制删除器、循环引用

2017-04-15 16:54 323 查看
上一篇我们已经详细讲解了智能指针的基础性知识和auto_ptr的模拟实现。

今天呢我们来讲解boost库的发展。

在C++11标准出来之前,C++98标准中都一直只有一个智能指针auto_ptr,我们知道,这是一个失败的设计。它的本质是管理权的转移,这有许多问题。而这时就有一群人开始扩展C++标准库的关于智能指针的部分,他们组成了boost社区,他们负责boost库的开发和维护。其目的是为C++程序员提供免费、同行审查的、可移植的程序库。boost库可以和C++标准库完美的共同工作,并且为其提供扩展功能。现在的C++11标准库的智能指针很大程度上“借鉴”了boost库。

1,scoped_ptr的模拟实现

scoped_ptr是一种简单粗暴的设计,它本质就是防拷贝,避免出现管理权的转移。这是它的最大特点,所以他的拷贝构造和赋值运算符重载只是只声明不定义,但是为了防止有的人在类外定义,所以将函数声明为protected。但这则也是他最大的问题所在,就是不能赋值拷贝,也就是说功能不全。

template<class T>
class ScopedPtr
{
public:
ScopedPtr(T* ptr = NULL)
:_ptr(ptr)
{
cout << "ScopedPtr" << endl;
}

~ScopedPtr()
{
delete _ptr;
cout << "~ScopedPtr" << endl;
}

T& operator*()
{
return *_ptr;
}

T* operator->()
{
return _ptr;
}

bool operator==(const ScopedPtr<T>& s)
{
return _ptr == s._ptr;
}

bool operator!=(const ScopedPtr<T>& s)
{
return _ptr != s._ptr;
}

void Reset(T* ptr = NULL)
{
if (_ptr != ptr)
{
delete _ptr;
}
_ptr = ptr;
}
protected:

ScopedPtr(ScopedPtr<T>& s); //防拷贝(只声明不定义,为防止别人在类外定义,就将他声明为protected)
ScopedPtr<T>& operator=(ScopedPtr<T>& s);

protected:
T* _ptr;
};


2,shared_ptr的模拟实现

这是比较完善的一个智能指针,他是通过指针保持某个对象的共享拥有权的智能指针。若干个shared_ptr对象可以拥有同一个对象,该对象通过维护一个引用计数,记录有多少个shared_ptr指针指向该对象,最后一个指向该对象的shared_ptr被销毁或重置时,即引用计数变为0时,该对象被销毁。销毁对象时使用的是delete表达式或是在构造shared_ptr时传入的自定义删除器(delete),这后面会有详细讲解,但是shared_ptr指针同样拥有缺陷,那就是循环引用,和线程安全问题,这也在后面讲解。先来模拟实现一下shared_ptr指针。

template<class T>
class SharedPtr
{
public:
SharedPtr(T* ptr = NULL)
:_ptr(ptr),_count(new int(0))
{
if (_ptr != NULL)
{
(*_count)++;
}
}

SharedPtr(const SharedPtr<T>& s)
{
_ptr = s._ptr;
_count = s._count;
if (_ptr != NULL)
{
(*_count)++;
}
}

SharedPtr<T>& operator=(const SharedPtr<T>& s)
{
if (this != &s)   //排除自己给自己赋值
{
if (--(*_count) <= 0)  //正常情况赋值
{
delete _ptr;
delete _count;
}
else  //指向同一个对象的指针互相赋值
{}
_ptr = s._ptr;
_count = s._count;
(*_count)++;
}
return *this;
}

~SharedPtr()
{
if (--(*_count) == 0)
{
delete _ptr;
delete _count; //别忘了delete维护的引用计数
}
}

T& operator*()
{
return *_ptr;
}

T* operator->()
{
return _ptr;
}

bool operator ==(const SharedPtr<T>& S)
{
return (_ptr == s._ptr);
}

bool operator !=(const SharedPtr<T>& S)
{
return (_ptr != s._ptr);
}

protected:
T* _ptr;
int* _count;
};


线程安全问题

因为使用引用计数值位判定指标,所以在多线程的环境下是不安全的。会因线程调用的先后顺序不同导致错误产生。对于这种问题,解决方法一般是加锁,对引用计数进行加,保证操作是互斥的。(这里暂且说这些,后续文章会有提及。)

循环引用问题

针对循环引用,我来举个例子使大家能更好的理解。看下面代码:

struct ListNode
{
int _data;
shared_ptr<ListNode> _next;
shared_ptr<ListNode> _prev;

ListNode(int x)
:_data(x),_next(NULL),_prev(NULL)
{}
~ListNode()
{
cout << "~ListNode()" << endl;
}
};

void test()
{
shared_ptr<ListNode> A(new ListNode(1));
shared_ptr<ListNode> B(new ListNode(2));

//if (A && B)  //将这五行代码放开来会出现什么情况
//{
//  A->_next = B;
//  B->_prev = A;
//}

cout << "A._count:" << A.use_count() << endl;
cout << "B._count:" << B.use_count() << endl;
}

int main()
{
test();
system("pause");
return 0;
}


运行结果



但是如果我将上面代码中屏蔽的那五行放开来,会出现什么结果呢?看下图:



有什么不同?对比上图,可以发现下面图的两个节点维护的引用计数值为2,他们也没有调用析构函数造成内存泄露。这是什么原因造成的?我们用一张图来解释。



而要解决循环引用的问题,就牵扯到了我们后面将要讲的一个指针weak_ptr。具体看后面。

定制删除器(仿函数)

经上面分析,我们可以看到,上面的指针不能用于文件的关闭,也不能用于管理mallocnew[]开辟的动态内存的释放,所以我们可以运用仿函数来定制删除器。如下:

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;
}


3,weak_ptr

weak_ptr是一个辅助性的智能指针,结合shared_ptr指针使用,它的本质就是弱引用,并不增加引用计数值。他没有实现->和*运算符的重载,所以不能直接用它访问对象。针对循环引用这个问题,就是因为不会引起引用计数值的改变,所以我们可以将_next和_prev定义为weak_ptr指针,这样就很好地解决了循环引用的问题。

struct ListNode
{
int _data;
weak_ptr<ListNode> _next;  //定义为weak_ptr指针
weak_ptr<ListNode> _prev;

ListNode(int x)
:_data(x),_next(NULL),_prev(NULL)
{}
~ListNode()
{
cout << "~ListNode()" << endl;
}
};


boost库剩余的两个指针:auto_arrshared_arr.这两个都是管理数组的,因为之前几个指针的析构函数中都是delete,不能对数组进行释放,所以我们自己定制删除器,而这两个指针就是专门管理指针的。下面来模拟实现一下。

模拟实现auto_arr

template<class T>
class AutoArr
{
public:
AutoArr(T* ptr = NULL)
:_ptr(ptr)
{}

~AutoArr()
{
delete[] _ptr;
}

AutoArr(const AutoArr<T>& s)
{
_ptr = s._ptr;
s._ptr = NULL;
}

AutoArr<T>& operator=(const AutoArr<T>& s)
{
if (this != &s)
{
_ptr = s._ptr;
s._ptr = NULL;
}
return *this;
}

T& operator[](size_t pos)
{
if (_ptr == NULL)
{
throw a;
}
return *(_ptr+pos);
}

void set(T* ptr)
{
int i = 0;
while (*(ptr + i))
{
*(_ptr + i) = *(ptr + i);
i++;
}
}

protected:
T* ptr;
};


模拟实现shared_arr

template<class T>

class SharedArr
{
public:
SharedArr(T* ptr = NULL)
:_ptr(ptr),_count(new int(0))
{
(*_count)++;
}

~SharedArr()
{
delete[] _ptr;
}

SharedArr(const SharedArr<T>& s)
{
_ptr = s._ptr;
(*_count)++;
}

SharedArr<T>& operator=(const SharedArr<T>& s)
{
if (this != &s)
{
if (--(*_count) <= 0)
{
delete _ptr;
delete _count;
}
else
{ }
_ptr = s._ptr;
_count = s._count;
(*_count)++;
}
}

T& operator[](size_t pos)
{
if (_ptr == NULL)
{
throw 1;
}
return *(_ptr + pos);
}

protected:
T* _ptr;
int* _count;
};
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  智能指针 boost库 c++