C++四种智能指针小结

来源:互联网 发布:达尔文的资料知和名言 编辑:程序博客网 时间:2024/06/05 10:37

C++四种智能指针auto_ptr、scope_ptr、shared_ptr和weak_ptr。其中auto_ptr是C++98标准化引入的;scope_ptr、shared_ptr和weak_ptr是C++11标准化才引入的(当然,早在C++03的TR1中就已经可以使用了)。我们都知道,auto_ptr虽说简单,但使用起来却到处是坑,以至于大家都不提倡使用,因此在C++11中已经明确被废弃了。shared_ptr是引用计数的智能指针,被奉为裸指针的完美替身,因此被广泛使用。也可能正是这个原因,scope_ptr 和 weak_ptr似乎被大家遗忘了(或许还会以为它们纯属多余)。

1. auto_ptr

auto_ptr 是C++98标准库里面一个轻量级的智能指针的实现,存在于头文件 memory中。其就是内部使用一个成员变量,指向一块内存资源(构造函数),并在析构函数中释放内存资源。从而达到RAII的目的。但是,auto_ptr 的拷贝物与被拷贝物之间是非等价的——这正是导致其到处是坑的根源所在。
我们仔细观察 auto_ptr 的源码就会发现 auto_ptr 的拷贝构造函数和拷贝赋值操作符函数所接受的参数类型都是非const 的引用类型(auto_ptr<_Ty>& , 即我们可以且需要修改源对象),而不是我们一般应该使用的const引用类型,查看源码我们会发现:

         auto_ptr(auto_ptr<_Ty>& _Right) _THROW0()                 : _Myptr(_Right.release())                 {        // construct by assuming pointer from _Right auto_ptr                 }         template<class _Other>                 auto_ptr<_Ty>& operator=(auto_ptr<_Other>& _Right) _THROW0()                 {        // assign compatible _Right (assume pointer)                 reset(_Right.release());                 return (*this);                 }

2. scoped_ptr

scoped_ptr 有着与 auto_ptr 类似的特性。scoped_ptr 与 auto_ptr 间最大的区别主要在于对内存资源拥有权的处理。auto_ptr 在拷贝(复制)时会从源 auto_ptr 自动交出拥有权,而 scoped_ptr 则不允许被复制(正是这一限制,使我们对scoped_ptr 的使用变得有信心且容易)。

3、shared_ptr

shared_ptr 被视为裸指针的克星。你可以像使用普通对象一样使用它,且获得与裸指针一样的行为(接口),并可以获得运行时的多态。

4、weak_ptr

weak_ptr 是为配合 shared_ptr 而引入的一种智能指针来协助shared_ptr 工作,它可以从一个 shared_ptr 或另一个 weak_ptr 对象构造,它的构造和析构不会引起引用记数的增加或减少。没有重载*和->但可以使用lock获得一个可用的shared_ptr对象。
weak_ptr 的实现代码可以简化如下:

template<class T> class weak_ptr   {   public:       weak_ptr();       template<class Y> weak_ptr(const shared_ptr<Y> & r);       weak_ptr(const weak_ptr & r);       ~weak_ptr();       weak_ptr & operator=(weak_ptr const & r);       long use_count()const;       //判断是否过期       bool expired()const;       //得到一个空的或者被协助的shared_ptr       shared_ptr<T> lock()const;       void reset();       void swap(weak_ptr<T> & b);   };  

weak_ptr 的一个重要用途是“打破循环引用”,即解决 shared_ptr (引用计数)在循环引用中的“无能”。例如下面的代码:

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\n"; }   public:       children_ptr children;   };   class children   {   public:       ~children() { std::cout <<"destroying children\n"; }   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...\n";       test();       std::cout<<"end test.\n";   }  

运行该程序可以看到,即使退出了test函数后,由于parent和children对象互相引用,它们的引用计数都是1,不能自动释放,并且此时这两个对象再无法访问到。这就引起了c++中那臭名昭著的内存泄漏。
一般来讲,解除这种循环引用有下面有三种可行的方法:
- 当只剩下最后一个引用的时候需要手动打破循环引用释放对象。
- 当parent的生存期超过children的生存期的时候,children改为使用一个普通指针指向parent。
- 使用弱引用的智能指针打破这种循环引用。
虽然这三种方法都可行,但方法1和方法2都需要程序员手动控制,麻烦且容易出错。我们知道,由于 weak_ptr 不更改引用计数,类似普通指针,因此只要把循环引用的一方使用 weak_ptr ,即可解除循环引用。所以使用 weak_ptr 则可以比较容易的解决这个问题。对于上面的那个例子来说,只要把children的定义改为如下方式,即可解除循环引用。

class children   {   public:       ~children() { std::cout <<"destroying children\n"; }   public:       boost::weak_ptr<parent> parent;   };  

值得一提的是, 虽然通过 weak_ptr 指针可以有效的解除循环引用,但这种方式必须在程序员能预见会出现循环引用的情况下才能使用,也可以是说这个仅仅是一种编译期的解决方案,如果程序在运行过程中出现了循环引用,还是会造成内存泄漏的。因此,不要认为只要使用了智能指针便能杜绝内存泄漏。毕竟,对于C++来说,由于没有垃圾回收机制,内存泄漏对每一个程序员来说都是一个非常头痛的问题。

5. 小结

auto_ptr、scope_ptr、shared_ptr 和 weak_ptr 都应该在什么情况使用(怎么选择)?
auto_ptr: 不提倡使用,即任何情况下都不要使用它。
scope_ptr: 在局部作用域(例如函数内部或类内部),且不需要将指针作为参数来传递的情况下使用。scope_ptr的开销较 shared_ptr 小一些。
shared_ptr: 似乎大部分情况下都可以使用它。1)在scope_ptr可以使用的情况如果使用shared_ptr 的话将会带来较大的开销;2)如果需要讲指针作为参数或者函数的返回值传递的话,则只能使用shared_ptr。
weak_ptr :打破循环引用,解除shared_ptr使用的尴尬。


0 0
原创粉丝点击