简单介绍
内存管理一直是 C++ 一个比較繁琐的问题,而智能指针却能够非常好的解决问题,在初始化时就已经预定了删除。排解了后顾之忧。1998年修订的第一版C++标准仅仅提供了一种智能指针:std::auto_ptr(现以废弃),它基本上就像是个普通的指针:通过地址来訪问一个动态分配的对象。
std::auto_ptr之所以被看作是智能指针。是由于它会在析构的时候调用delete操作符来自己主动释放所包括的对象。
当然这要求在初始化的时候,传给它一个由new操作符返回的对象的地址。既然std::auto_ptr的析构函数会调用delete操作符。它所包括的对象的内存会确保释放掉。
这是智能指针的一个长处。
当尝试和异常联系起来时这就更加重要了:没有std::auto_ptr这种智能指针。每个动态分配内存的函数都须要捕捉全部可能的异常。以确保在异常传递给函数的调用者之前将内存释放掉。Boost C++ 库的智能指针系列提供了很多能够用在各种场合的智能指针。
RAII
RAII全称是“Resource acquisition is initialization”,直译为“资源获取就是初始化”。
可是这翻译并没有显示出这个惯使用方法的真正内涵。RAII的优点在于它提供了一种资源自己主动管理的方式。当产生异常、回滚等现象时,RAII能够正确地释放掉资源。
RAII也是智能指针的基本原理,智能指针仅仅是这个习语的当中一例。智能指针确保在不论什么情况下。动态分配的内存都能得到正确释放。从而将开发者从这项任务中解放了出来。 这包括程序由于异常而中断。原本用于释放内存的代码被跳过的场景。用一个动态分配的对象的地址来初始化智能指针,在析构的时候释放内存,就确保了这一点。
由于析构函数总是会被运行的。这样所包括的内存也将总是会被释放。
很多的 C++ 应用程序都须要动态管理内存,因而智能指针是一种非常重要的 RAII 类型。只是 RAII 本身是适用于很多其他场景的。
scoped_ptr
boost::scoped_ptr是一个简单的智能指针,它可以保证在离开作用域后对象被自己主动释放。
boost::scoped_ptr的实现是利用了一个栈上的对象去管理一个堆上的对象。从而使得堆上的对象随着栈上的对象销毁时自己主动删除。
boost::scoped_ptr 特点
- 不能转换全部权
scoped_ptr所管理的对象生命周期只局限于一个区间(该指针所在的"{}"之间)。无法传到区间之外,这就意味着scoped_ptr对象是不能作为函数的返回值的。 - 不能共享全部权
这个特点一方面使得该指针简单易用。还有一方面也造成了功能的薄弱:不能用于STL的容器中。
- 不能用于管理数组对象
因为scoped_ptr是通过delete来删除所管理对象的。而数组对象必须通过deletep[]来删除。因此scoped_ptr是不能管理数组对象的,假设要管理数组对象须要使用boost::scoped_array类。
shared_ptr
boost::scoped_ptr尽管简单易用。但它不能共享全部权的特性却大大限制了其使用范围。shared_ptr能够解决这一局限。
在标准C++中为 std::shared_ptr ,在Boost C++库里。这个智能指针命名为boost::shared_ptr。
shared_ptr的管理机制事实上并不复杂,就是对所管理的对象进行了引用计数,当新增一个shared_ptr对该对象进行管理时,就将该对象的引用计数加一;降低一个shared_ptr对该对象进行管理时,就将该对象的引用计数减一。假设该对象的引用计数为0的时候,说明没有不论什么指针对其管理,才调用delete释放其所占的内存。
boost::shared_ptr 特点
- 能够共享对象的全部权
和scoped_ptr相比shared_ptr能够共享对象的全部权。所以保存在容器中的拷贝(包含容器在须要时额外创建的拷贝)都是和原件同样的。能够在标准容器中安全的使用动态分配的对象。 - 线程安全的
shared_ptr 对象提供与内建类型一样的线程安全级别。一个 shared_ptr 实例能够同一时候被多个线程“读”(仅使用不变操作进行訪问)。 不同的 shared_ptr 实例能够同一时候被多个线程“写入”(使用类似 operator= 或 reset 这种可变操作进行訪问)(即使这些实例是拷贝,并且共享下层的引用计数)。 不论什么其他的同一时候訪问的结果会导致没有定义行为。”- 同一个shared_ptr被多个线程“读”是安全的。
- 同一个shared_ptr被多个线程“写”是不安全的。
- 共享引用计数的不同的shared_ptr被多个线程”写“是安全的。
- 支持自己定义的deleter
boost::shared_ptr 注意事项
- shared_ptr能够当做函数的參数,也能够当做函数的返回值,这时候相当于使用复制构造
- shared_ptr能够被用于标准容器,复制时相当于使用复制构造
- 要注意不要循环引用,那样会造成对象不会被释放
weak_ptr
shared_ptr引用计数是一种便利的内存管理机制,但它有一个非常大的缺点,那就是不能管理循环引用的对象。比如:
#include <string> #include <iostream> #include <boost/shared_ptr.hpp> #include <boost/weak_ptr.hpp> class parent; class children; typedef boost::shared_ptr<parent> parent_ptr; typedef boost::shared_ptr<children> children_ptr; class parent { public: ~parent() { std::cout <<"destroying parent "; } public: children_ptr children; }; class children { public: ~children() { std::cout <<"destroying children "; } public: parent_ptr parent; }; void test() { parent_ptr father(new parent()); children_ptr son(new children); father->children = son; son->parent = father; } void main() { std::cout<<"begin test... "; test(); std::cout<<"end test. "; }
执行该程序能够看到,即使退出了test函数后,因为parent和children对象互相引用,它们的引用计数都是1。不能自己主动释放,而且此时这两个对象再无法訪问到,这就引起了内存泄漏。
一般来讲,解除这样的循环引用有以下有三种可行的方法:
- 当仅仅剩下最后一个引用的时候须要手动打破循环引用释放对象。
- 当parent的生存期超过children的生存期的时候。children改为使用一个普通指针指向parent。
- 使用弱引用的智能指针打破这样的循环引用。
在父对子引用时使用强引用,子对父引用时使用弱引用,从而避免了循环引用。
尽管这三种方法都可行。但方法1和方法2都须要程序猿手动控制,麻烦且easy出错。
这里主要介绍一下第三种方法和boost中的弱引用的智能指针boost::weak_ptr。
强引用
一个强引用当被引用的对象活着的话。这个引用也存在(就是说,当至少有一个强引用,那么这个对象就不能被释放)。
boost::share_ptr就是强引用。
弱引用
它不过对象存在时候的引用。当对象不存在时弱引用可以检測到,从而避免非法訪问,弱引用也不会改动对象的引用计数。这意味这弱引用它并不正确对象的内存进行管理,在功能上类似于普通指针,然而一个比較大的差别是。弱引用能检測到所管理的对象是否已经被释放。从而避免訪问非法内存。
在C++中,普通指针可看做弱引用,智能指针可看做强引用。虽然指针不能算"真正"的弱引用,由于弱引用应该能知道何时对象变成不可訪问的了。
打破循环引用
在父对子引用时使用强引用,子对父引用时使用弱引用,从而避免了循环引用。
//具体样例见 Boost智能指针-weak_ptr class children { public: ~children() { std::cout <<"destroying children "; } public: boost::weak_ptr<parent> parent; };
尽管通过弱引用指针能够有效的解除循环引用,但这样的方式必须在程序猿能预见会出现循环引用的情况下才干使用。也能够是说这个不过一种编译期的解决方式,假设程序在执行过程中出现了循环引用。还是会造成内存泄漏的。
intrusive_ptr
在多数情况下,我们不使用 boost::intrusive_ptr, 由于共享全部权的功能已在 boost::shared_ptr中提供。并且非插入式智能指针比插入式智能指针更灵活。
可是。有时候也会须要插入式的引用计数,可能是因为旧的代码,或者是为了与第三方的类进行集成。当有这样的须要时。能够用 intrusive_ptr ,它具有与其他Boost智能指针同样的语义。假设你使用过其他的Boost智能指针。你就会发现不论是否插入式的,全部智能指针都有一致的接口。
使用intrusive_ptr的类必须能够提供引用计数。intrusive_ptr 通过调用两个函数。intrusive_ptr_add_ref 和 intrusive_ptr_release来管理引用计数。这两个函数必须正确地操作插入式的引用计数,以保证 intrusive_ptr正确工作。
在使用intrusive_ptr的类中已经内置有引用计数的情况下,实现对intrusive_ptr的支持就是实现这两个函数。
有些情况下,能够创建这两个函数的參数化版本号,然后对全部带插入式引用计数的类型使用同样的实现。
多数时候,声明这两个函数的最好的地方就是它们所支持的类型所在的名字空间。 在下面情况时使用 intrusive_ptr
- 须要把 this 当作智能指针来使用。
- 已有代码使用或提供了插入式的引用计数。
- 智能指针的大小必须与裸指针的大小相等。
对照boost::shared_ptr
使用boost::shared_ptr用户类本省不须要具有引用计数功能。而是由boost::shared_ptr来提供。使用boost::shared_ptr的一大陷阱就是用一个raw pointer多次创建boost::shared_ptr。这将导致该raw pointer被多次销毁当boost::shared_ptr析构时。即不能例如以下使用:
int *a = new int(5);
boost::shared_ptr ptr1(a);
boost::shared_ptr ptr2(a); //错误!
boost::intrusive_ptr全然具备boost::shared_ptr的功能,且不存在shared_ptr的问题,即能够利用raw pointer创建多个intrusive _ptr,其原因就在于引用计数的ref_count对象,shared_ptr是放在shared_ptr结构里,而目标对象T通过继承intrusive_ptr_base将引用计数作为T对象的内部成员变量,就不会出现同一个对象有两个引用计数器的情况出现。
那么为什么通常鼓舞大家使用shared_ptr,而不是intrusive_ptr呢, 在于shared_ptr不是侵入性的。能够指向随意类型的对象; 而intrusive_ptr所要指向的对象。须要继承intrusive_ptr_base,即使不须要,引用计数成员也会被创建。
參考链接
http://zh.wikipedia.org/wiki/%E5%BC%B1%E5%BC%95%E7%94%A8 http://www.codeproject.com/Articles/8394/Smart-Pointers-to-boost-your-code http://www.cnblogs.com/TianFang/archive/2008/09/15/1291050.html http://www.cnblogs.com/TianFang/archive/2008/09/19/1294521.html
http://www.cnblogs.com/TianFang/archive/2008/09/20/1294590.html http://blog.csdn.net/alai04/article/details/572959 http://blog.csdn.net/yockie/article/details/8840205 http://blog.csdn.net/juana1/article/details/6624222 http://blog.csdn.net/ithzhang/article/details/9038929
http://blog.csdn.net/hunter8777/article/details/6327704 http://blog.csdn.net/yusiguyuan/article/details/22037833
From:http://blog.csdn.net/liufei_learning/article/details/34808549
版权声明:本文博主原创文章,博客,未经同意不得转载。