stl中auto_ptr,unique_ptr,shared_ptr,weak_ptr四种智能指针的使用总结
(1)auto_ptr
主要用于解决资源自动释放的问题。防止用户忘记delete掉new申请的内存空间。使用auto_ptr会在离开变量的作用域之后直接调用析构函数进行资源释放。
void Function() { auto_ptr<Obj> ptr(new Obj(20)); ... if (error occur) throw exception... }
但是,这是一种被c++11标准废弃的一个智能指针,原因是一旦将autoptr对象赋值给另一个对象,原来的智能指针所指的将是空的地址。再使用该指针就是错误的了。
(2)unique_ptr
unique_ptr指针可以看成auto_ptr的替代品。因为他对对象的引用是唯一的,不能随便进行转移。这种专一是通过无法进行unique_ptr对象的赋值和拷贝构造来实现的。
另外,unique_ptr可以进行移动构造和移动赋值操作。
如果一定要用unique_ptr实现auto_ptr的功能,也就是一定要把一个unique_ptr赋值给另一个,那就一定要通过移动函数来实现。由于是手动操作,因此需要程序员格外注意。
unique<Obj> ptr1(new Obj());
unique<Obj> ptr2(std::move(ptr1));
(3)shared_ptr
auto_ptr和unique_ptr都只能一个智能指针引用对象,而shared_ptr则是可以多个智能指针同时拥有一个对象。
shared_ptr实现方式就是使用引用计数。引用计数的原理是,多个智能指针同时引用一个对象,每当引用一次,引用计数加一,每当智能指针销毁了,引用计数就减一,当引用计数减少到0的时候就释放引用的对象。这种引用计数的增减发生在智能指针的构造函数,复制构造函数,赋值操作符,析构函数中。
这种方式使得多个智能指针同时对所引用的对象有拥有权,同时在引用计数减到0之后也会自动释放内存,也实现了auto_ptr和unique_ptr的资源释放的功能。
由于shared_ptr支持复制构造,所以它可以作为标准库容器中的元素。这是auto_ptr和unique_ptr所不能实现的。
(4)weak_ptr
shared_ptr是一种强引用的关系,智能指针直接引用对象。那么这个会代码一个隐含的问题,就是循环引用,从而造成内存泄漏,即便是java语言有自己的垃圾回收器,对这种内存泄漏也没有办法,所以循环引用对java程序员来说也是一个很值得注意的问题。首先来看一个循环引用的例子。
class Parent { public: shared_ptr<Child*> child; }; class Child { public: shared_ptr<Parent*> parent; }; void Function(){ shared_ptr<Parent*> pA(new Parent); //Parent的引用计数为1 shared_ptr<Child*> pB(new Child); //Child引用计数为1 pA->child = pB; //使用了赋值函数,因此Child引用计数增加1,变为2 pB->parent = pA; //使用了赋值函数,因此Parent引用计数增加1,变为2 // pB先出作用域,pB的引用计数减少为1,不为0,因此堆上的pb所指空间没有被释放;同理pA }
所以在使用基于引用计数的智能指针时,要特别小心循环引用带来的内存泄漏,循环引用不只是两方的情况,只要引用链成环都会出现问题。当然循环引用本身就说明设计上可能存在一些问题。
这就是使用强引用所带来的问题。使用weak_ptr解决
weak_ptr从字面意思上可以看出是一个弱指针,不是说明这个指针的能力比较弱,而是说他对他所引用的对象的所有权比较弱。说得更直接一点儿就是他并不拥有所引用对象的所有权,而且他还不能直接使用他所引用的对象。
在stl中,weak_ptr是和shared_ptr配合使用的,在实现shared_ptr的时候也就考虑了weak_ptr的因素。weak_ptr是shared_ptr的观察者,它不会干扰shared_ptr所共享对象的所有权,当一个weak_ptr所观察的shared_ptr要释放它的资源时,它会把相关的weak_ptr的指针设置为空,防止weak_ptr持有悬空的指针。
weak_ptr并不拥有资源的所有权,所以不能直接使用资源。可以从一个weak_ptr构造一个shared_ptr以取得共享资源的所有权。
void Function() { shared_ptr<int> sp(new Obj()); assert(sp.use_count() == 1); weak_ptr<int> wp(sp); //从shared_ptr创建weak_ptr assert(wp.use_count() == 1); if (!wp.expired())//判断weak_ptr观察的对象是否失效 { shared_ptr<int> sp2 = wp.lock();//获得一个shared_ptr *sp2 = 100; assert(wp.use_count() == 2); } assert(wp.use_count() == 1); return 0; }
weak_ptr并没有重载-> 和 * 操作符,所以我们不能通过他来直接使用资源,我们可以通过lock来获得一个shared_ptr对象来对资源进行使用。如果引用的资源已经释放,lock()函数将返回一个存储空指针的shared_ptr。expired函数用来判断资源是否失效。
使用weak_ptr并不会增加资源的引用计数。所以对资源的引用是弱引用,利用这个特性可以解决前面所说的循环依赖问题。
class Parent { public: weak_ptr<Child> child; }; class Child { public: weak_ptr<Parent> parent; }; void Function(){ shared_ptr<Parent> pA(new Parent); shared_ptr<Child> pB(new Child); pA->child = pB; pB->parent = pA; }
这个时候第三和第四条语句的执行并没有增加引用计数,从而在函数执行完成只有能自动释放内存。
从上面的分析可以看出,weak_ptr是一种辅助shared_ptr的一种智能指针,一般不单独使用,而是结合shared_ptr一起使用。
总结:
1. 尽量使用unique_ptr而不要使用auto_ptr
2. 一般来说shared_ptr能够满足我们大部分的需求
3. weak_ptr可以避免递归的依赖关系