智能指针(下)-----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。但这则也是他最大的问题所在,就是不能赋值拷贝,也就是说功能不全。
2,shared_ptr的模拟实现
这是比较完善的一个智能指针,他是通过指针保持某个对象的共享拥有权的智能指针。若干个shared_ptr对象可以拥有同一个对象,该对象通过维护一个引用计数,记录有多少个shared_ptr指针指向该对象,最后一个指向该对象的shared_ptr被销毁或重置时,即引用计数变为0时,该对象被销毁。销毁对象时使用的是delete表达式或是在构造shared_ptr时传入的自定义删除器(delete),这后面会有详细讲解,但是shared_ptr指针同样拥有缺陷,那就是循环引用,和线程安全问题,这也在后面讲解。先来模拟实现一下shared_ptr指针。
线程安全问题
因为使用引用计数值位判定指标,所以在多线程的环境下是不安全的。会因线程调用的先后顺序不同导致错误产生。对于这种问题,解决方法一般是加锁,对引用计数进行加锁,保证操作是互斥的。(这里暂且说这些,后续文章会有提及。)
循环引用问题
针对循环引用,我来举个例子使大家能更好的理解。看下面代码:
运行结果
但是如果我将上面代码中屏蔽的那五行放开来,会出现什么结果呢?看下图:
有什么不同?对比上图,可以发现下面图的两个节点维护的引用计数值为2,他们也没有调用析构函数造成内存泄露。这是什么原因造成的?我们用一张图来解释。
而要解决循环引用的问题,就牵扯到了我们后面将要讲的一个指针weak_ptr。具体看后面。
定制删除器(仿函数)
经上面分析,我们可以看到,上面的指针不能用于文件的关闭,也不能用于管理malloc和new[]开辟的动态内存的释放,所以我们可以运用仿函数来定制删除器。如下:
3,weak_ptr
weak_ptr是一个辅助性的智能指针,结合shared_ptr指针使用,它的本质就是弱引用,并不增加引用计数值。他没有实现->和*运算符的重载,所以不能直接用它访问对象。针对循环引用这个问题,就是因为不会引起引用计数值的改变,所以我们可以将_next和_prev定义为weak_ptr指针,这样就很好地解决了循环引用的问题。
boost库剩余的两个指针:auto_arr和shared_arr.这两个都是管理数组的,因为之前几个指针的析构函数中都是delete,不能对数组进行释放,所以我们自己定制删除器,而这两个指针就是专门管理指针的。下面来模拟实现一下。
模拟实现auto_arr
模拟实现shared_arr
今天呢我们来讲解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。具体看后面。
定制删除器(仿函数)
经上面分析,我们可以看到,上面的指针不能用于文件的关闭,也不能用于管理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; }
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_arr和shared_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库在工作(8)引用计数的智能指针shared_ptr之一
- C++ 智能指针——简单实现以及循环引用问题
- 关于boost 库 shared_ptr 智能指针的循环引用【2013.10.22】
- boost库在工作(9)引用计数的智能指针shared_ptr之二
- c++ 智能指针及 循环引用问题
- [zz] C++智能指针循环引用解决
- C++ — 智能指针的简单实现以及循环引用问题
- c++智能指针循环引用带来的问题及解决方案
- boost库在工作(11)引用计数的智能指针weak_ptr
- boost库在工作(8)引用计数的智能指针shared_ptr之一
- boost库在工作(11)引用计数的智能指针weak_ptr
- boost库在工作(8)引用计数的智能指针shared_ptr之一
- C++ — 智能指针的简单实现以及循环引用问题
- 智能指针的死穴 -- 循环引用
- 【C++】智能指针简述(五):解决循环引用的weak_ptr
- C++智能指针(三):weak_ptr--解决shared_ptr循环引用问题
- 智能指针的模拟实现shared_ptr 循环引用 定置删除器
- c++ 智能指针及 循环引用问题
- boost库在工作(10)引用计数的智能指针shared_array
- 智能指针的循环引用和如何解决循环引用