浅谈智能指针的历史包袱
Posted tp_16b
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了浅谈智能指针的历史包袱相关的知识,希望对你有一定的参考价值。
auto_ptr
/************************************************************************* > File Name: AutoPtr.cc > Author: tp > Mail: > Created Time: Sun 13 May 2018 05:08:41 PM CST ************************************************************************/ #include <iostream> using namespace std; struct A { int age; string name; A(string na, int a = 0) :age(a), name(na) { } }; template <class T> class AutoPtr { public: AutoPtr(T* ptr) :_ptr(ptr) { } T& operator*() { return *_ptr; } T* operator->() { return _ptr; } //ap2( ap1) AutoPtr(AutoPtr<T>& a) //防止多次析构,ap2直接夺取管理权,ap1._ptr置空 :_ptr(a._ptr) { a._ptr = NULL; } //ap2 = ap1 AutoPtr<T>& operator=(AutoPtr<T>& a) { if(this != &a) { delete _ptr; _ptr = a._ptr; a._ptr = NULL; } return *this; } ~AutoPtr( ) { cout<<"AutoPtr帮助析构\\n"; delete _ptr; } protected: T* _ptr; }; int main( void) { AutoPtr<int> ap1(new int(20)); cout<<++(*ap1)<<endl; AutoPtr<A> ap2(new A("napom")); cout<<ap2->name<<endl; return 0; }
这里这两个地方,是需要弄清楚的
然后,看一手结果:
unique_ptr (scoped_ptr)
由于早期的auto_ptr抢权思想存在严重的缺陷,所以auto_ptr通常是被严禁使用的。但是从C++98到C++11出来期间,C++标准库却迟迟没有给出改良版的智能指针,但是在此期间有一群大牛们就坐不住了,他们在叫一个boost的社区讨论,他们想要自己去造一个第三方库,好去解决C++中的一些诟病同时对c++进行一些完善。这里的scoped_ptr就是其中的一个杰作(后来在C++11出来之后,c++11参考boost版本将scoped_ptr改名为unique_ptr)。它的思想也是特别的直接,它做的就是暴力的防止用户调用拷贝构造和赋值运算符“=”进行夺权管理。它直接将拷贝构造和“=”运算符重载函数给封装起来,以此防止用户来调用或恶意的去实现它。
/************************************************************************* > File Name: ScopedPtr.cc > Author: tp > Mail: > Created Time: Sun 13 May 2018 06:02:24 PM CST ************************************************************************/ #include <iostream> using namespace std; struct A { int age; string name; A(string na, int a = 0) :age(a), name( na) { } }; template <class T> class ScopedPtr { public: ScopedPtr(T* ptr) :_ptr(ptr) { } T& operator*() { return *_ptr; } T* operator->() { return _ptr; } ~ScopedPtr( ) { cout<<"AutoPtr析构\\n"; delete _ptr; } protected: ScopedPtr(ScopedPtr<T>& a); ScopedPtr<T>& operator=( ScopedPtr<T>& a); T* _ptr; };
上面的scoped_ptr用于处理单个的对象,而new/delete和new[]/delete[]底层实现机制又是不同的(delete[]头部多开4字节,具体可戳这里)对于new[]出来的多个对象,boost又提供了一个scoped_array版本的智能指针用来管理对象数组,用法和scoped_ptr类似。同样的,上面的auto_ptr也有一个auto_array版本。
scoped_ptr的就好似“你强任你强”,我不给你机会施展,看你怎么夺权管理。这种做法虽然达到了上面的目的。但是这种暴力的思想却带来了新的问题。。由于scoped智能指针独享所有权,当我们真真需要复制智能指针时,需求便满足不了了。如此我们再引入一个更完善智能指针指针,专门用于处理复制,参数传递的情况。这便是如下的shared智能指针。
shared_ptr
> File Name: sharedPtr.cc > Author: tp > Mail: > Created Time: Sun 13 May 2018 06:11:02 PM CST ************************************************************************/ #include <iostream> using namespace std; struct A { int age; string name; A(string na, int a = 0) :age(a), name( na) { } }; //引用计数版本 template <class T> class SharedPtr { public: SharedPtr( T* ptr) :_ptr(ptr) ,_pCount( new int(1)) { } T* operator->( ) { return _ptr; } T& operator*( ) { return *_ptr; } SharedPtr(const SharedPtr<T>& sp) //加上const :_ptr(sp._ptr) ,_pCount(sp._pCount) { ++(*_pCount); } SharedPtr<T>& operator=(const SharedPtr<T>& sp) { if(this != &sp) { Release( ); _ptr = sp._ptr; _pCount = sp._pCount; ++(*_pCount); } return *this; } void Release( ) { if(--(*_pCount) == 0) { cout<<"释放空间"<<endl; delete _ptr; delete _pCount; _ptr = NULL; _pCount = NULL; } } ~SharedPtr() { //cout<<"析构调用"<<endl; Release( ); } protected: T* _ptr; int* _pCount; }; int main( void) { SharedPtr<A> sp1(new A("gene")); cout<<sp1->name<<endl; SharedPtr<A> sp2(sp1); cout<<sp2->name<<endl; SharedPtr<A> sp3(new A("codfish")); sp3 = sp2; cout<<sp3->name<<endl; return 0; }
- struct Node
- {
- shared_ptr<ListNode> _prev;
- shared_ptr<ListNode> _next;
- ListNode(int x)
- : _prev(NULL)
- ,_next(NULL)
- {}
- ~Node()
- {
- cout << "~Node" << endl;
- }
- };
- int main()
- {
- shared_ptr<Node> cur(new Node(1));
- shared_ptr<Node> next(new Node(2));
- cur->_next = next;
- next->_prev = cur;
- cout << "cur.count: " << cur.use_count() << endl;
- cout << "next.count: " << next.use_count() << endl;
- return 0;
- }
现在我们验证shared智能指针的缺陷,我们用系统给我们写的shared_ptr指针,然后再来构造两个双向链表里面的结点,这里这个双向链表可能有一点简陋,但是我们只是需要它的prev和next指针就够了。现在我们运行代码看看会发生什么情况:
weak_ptr
weak_ptr是为了配合shared_ptr而引入的一种智能指针,它更像是shared_ptr的一个助手而不是智能指针,因为它不具有普通指针的行为,没有重载operator*和->,它的最大作用在于协助shared_ptr工作,像旁观者那样观测资源的使用情况.具体一点讲就是,首先weak_ptr 是专门为shared_ptr 而准备的。它是 boost::shared_ptr 的观察者对象,作为观察者,那也就意味着weak_ptr它只对shared_ptr 进行引用却不会去改变其引用计数,当被观察的shared_ptr 失效后,相应的weak_ptr 也相应失效,然后它就什么都不管 光是个删 , 也就是这里的cur和next在析构的时候 ,它都不用引用计数减1(因为这里引用计数一直保持是1,可以直接释放资源) , 直接删除结点就好。这样也就间接地解决了循环引用的问题,当然week_ptr指针的功能不是只有这一个。但是现在我们只要知道它可以解决循环引用就好。
struct Node { // shared_ptr<Node> _prev; // shared_ptr<Node> _next; weak_ptr<Node> _prev; weak_ptr<Node> _next; // Node() // :_prev(NULL) // ,_next(NULL) // {} ~Node() { cout<<"~Node"<<endl; } }; void TestCycleReference( void) { shared_ptr<Node> cur( new Node); shared_ptr<Node> next( new Node); cur->_next = next; next->_prev = cur; cout<<"cur.count:"<<cur.use_count()<<endl; cout<<"next.count:"<<next.use_count()<<endl; }
1. 拷贝构造
1 constexpr weak_ptr() noexcept; 2 3 weak_ptr (const weak_ptr& x) noexcept; 4 template <class U> weak_ptr (const weak_ptr<U>& x) noexcept; 5 6 template <class U> weak_ptr (const shared_ptr<U>& x) noexcept;
如果一个参数x被传递,而x不是空的,那么弱ptr对象就变成了拥有的x组的一部分,在没有所有权本身的情况下(并且不增加它的使用数量),就可以访问该对象的资产。
如果x是空的,或者如果没有参数传递,构造的弱ptr是空的。
如果x是一个别名,弱ptr保存了所有的数据和存储的指针。
2.operator=重载
1 weak_ptr& operator= (const weak_ptr& x) noexcept; 2 template <class U> weak_ptr& operator= (const weak_ptr<U>& x) noexcept; 3 4 template <class U> weak_ptr& operator= (const shared_ptr<U>& x) noexcept
对象成为拥有的x组的一部分,允许对该对象的资源进行访问,直到过期,而不需要获得所有权本身(并且不增加其引用计数)。
如果x是空的,构造的弱ptr也是空的。
如果x是一个别名,弱ptr保存了所有的数据和存储的指针。
shared_ptr对象可以直接分配给弱ptr对象,但是为了将弱ptr对象分配给sharedptr,它应该使用成员锁(lock)来完成。
包括上面成员,它一共有这些成员
可参考cpulsplus官网:http://www.cplusplus.com/reference/memory/weak_ptr/
定置删除器
在前面用到了的shared_ptr的时候,如果够仔细,你可能注意C++11没有shared_array版本的智能指针,也就是说,对于new[]开出的对象数组,C++11好像没有相应的处理办法了? 当然,这是不可能的。C++11对于此,实际上站在了一个更高的角度来处理。虽然在boost中有_array版本的智能指针管理new[]出来的对象数组,但是实际中还有malloc,fopen..等方式打开资源,单纯的用delete关闭资源显然是力不从心的。所以对此,C++11提供用一个叫删除器的东西来帮助释放资源,它由程序员自己去定置。它的实现其实就是定义一个类出来,然后在类中重载了operator() 在该函数当中进行相应释放资源操作(由于重载了operator(),该类的实例就拥有函数的使用特性和功能.所以也把这样类的一个实例叫做仿函数)。如我们shared_ptr<int> sp(new int[2]),就可以实现一个这样的类
template<class T> struct DeleteArray { void operator()(T* ptr) { delete[] ptr; } } ;
然后可以DeleteArray da定置一个删除器da了 。随后,前面定义智能指针相应改成shared_ptr<int> sp(new int[2], da)即可。
其实它原理也比较简单,为了更方便剖析,我们这里来对上面自定义SharePtr改造,让它支持定置删除功能。
template<class T> struct DelArr { void operator()(T* ptr) { cout<<"delete[ ]"<<endl; delete[] ptr; } }; template <class T, class Del= DelArr<T> > //为模板参数添加默认值 class SharedPtr { public: SharedPtr( T* ptr) :_ptr(ptr) ,_pCount( new int(1)) { } T* operator->( ) { return _ptr; } T& operator*( ) { return *_ptr; } SharedPtr(const SharedPtr<T>& sp) //加上const :_ptr(sp._ptr) ,_pCount(sp._pCount) { ++(*_pCount); } SharedPtr<T>& operator=(const SharedPtr<T>& sp) { if(this != &sp) { Release( ); _ptr = sp._ptr; _pCount = sp._pCount; ++(*_pCount); } return *this; } void Release( ) { if(--(*_pCount) == 0) { if(_ptr) { cout<<"释放空间"<<endl; //delete _ptr;
//_ptr = NULL;
//_pCount = NULL; Del _del; //定义定置删除仿函数 _del(_ptr); //调用该仿函数 }
delete _pCount; } } ~SharedPtr() { cout<<"析构调用"<<endl; Release( ); } protected: T* _ptr; int* _pCount; }; void TestSharedPtr() { //SharedPtr<A, DelArr<A> > sp(new A[2]); SharedPtr<int, DelArr<int> >sp1(new int[2]); }
1 class Free 2 { 3 public: 4 void operator()(void* ptr) 5 { 6 free(ptr); 7 } 8 }; 9 struct FClose 10 { 11 void operator()(void* fp) 12 { 13 fclose((FILE*)fp); 14 } 15 }; 16 vod Test() 17 { 18 shared_ptr<void> sp1(malloc(sizeof(int)*3), Free()); 19 shared_ptr<FILE> sp2(fopen("test.txt", "r"), FClose()); 20 }
最后,智能指针大致原理就是这样,当然这只是本人很粗浅的一点总结,对智能指针使用还有很多东西需要探索。
不过为了平常使用智能指针能更高效的使用,最后小结一下
·在可以使用 boost 库的场合下,拒绝使用 std::auto_ptr,因为其不仅不符合 C++ 编程思想。
·在确定对象无需共享的情况下,使用 boost::scoped_ptr。
·在对象需要共享的情况下,使用 boost::shared_ptr。
·在需要访问 boost::shared_ptr 对象,而又不想改变其引用计数的情况下(循环引用)使用boost::weak_ptr。
以上是关于浅谈智能指针的历史包袱的主要内容,如果未能解决你的问题,请参考以下文章
C++11之智能指针(unique_ptrshared_ptrweak_ptrauto_ptr)浅谈内存管理
浅谈ObjectARX智能指针AcDbObjectPointer的用法