std | boost | 功能说明 |
---|---|---|
unique_ptr | scoped_ptr | 独占指针对象,并保证指针所指对象生命周期与其一致 |
shared_ptr | shared_ptr | 可共享指针对象,可以赋值给shared_ptr或weak_ptr。 指针所指对象在所有的相关联的shared_ptr生命周期结束时结束,是强引用。 |
weak_ptr | weak_ptr | 它不能决定所指对象的生命周期,引用所指对象时,需要lock()成shared_ptr才能使用。 |
智能指针是利用RAII(Resource Acquisition Is Initialization:资源获取即初始化)来管理资源。关于RAII的讨论可以参考前面的《40_面向对象编程--虚函数与多态(六)》。在使用boost库之前应该先下载后放在某个路径,并在VS 包含目录中添加。下面是boost库里面的智能指针:
二、unique_ptr<T>(原来scoped_ptr<T>)
1、示例
#include <iostream> #include <memory> using namespace std; class X { public: X() { cout << "X ..." << endl; } ~X() { cout << "~X ..." << endl; } }; int main(void) { cout << "Entering main ..." << endl; { unique_ptr<X> pp(new X); //boost::unique_ptr<X> p2(pp); //Error:所有权不能转移 } cout << "Exiting main ..." << endl; return 0; }
2、源码分析
来稍微看一下scoped_ptr的简单定义:
namespace boost { template<typename T> class scoped_ptr : noncopyable { private: T *px; scoped_ptr(scoped_ptr const &); scoped_ptr &operator=(scoped_ptr const &); typedef scoped_ptr<T> this_type; void operator==( scoped_ptr const & ) const; void operator!=( scoped_ptr const & ) const; public: explicit scoped_ptr(T *p = 0); ~scoped_ptr(); explicit scoped_ptr( std::auto_ptr<T> p ): px( p.release() ); void reset(T *p = 0); T &operator*() const; T *operator->() const; T *get() const; void swap(scoped_ptr &b); }; template<typename T> void swap(scoped_ptr<T> &a, scoped_ptr<T> &b); }
与《40_面向对象编程--虚函数与多态(六)》的auto_ptr类似,内部也有一个T* px; 成员 ,智能指针对象pp 生存期到了,调用析构函数,在析构函数内会delete px; 当调用reset() 函数时也能够释放堆对象,如何实现的呢?
void reset(T *p = 0) // never throws { BOOST_ASSERT( p == 0 || p != px ); // catch self-reset errors this_type(p).swap(*this); } void swap(scoped_ptr &b) // never throws { T *tmp = b.px; b.px = px; px = tmp; }
typedef scoped_ptr<T> this_type; 当调用pp.reset(),reset 函数构造一个临时对象,它的成员px=0, 在swap 函数中调换 pp.px 与 (this_type)(p).px, 即现在pp.px = 0; //解绑临时对象接管了裸指针(即所有权可以交换),reset 函数返回,栈上的临时对象析构,调用析构函数,进而delete px;
另外拷贝构造函数和operator= 都声明为私有,故所有权不能转移,且因为容器的push_back 函数需要调用拷贝构造函数,故也不能将scoped_ptr 放进vector,这点与auto_ptr 相同(不能共享所有权)。此外,还可以使用 auto_ptr 对象 构造一个scoped_ptr 对象:scoped_ptr( std::auto_ptr<T> p ): px( p.release() );
由于scoped_ptr是通过delete来删除所管理对象的,而数组对象必须通过deletep[]来删除,因此boost::scoped_ptr是不能管理数组对象的,如果要管理数组对象需要使用boost::scoped_array类。
boost::scoped_ptr和std::auto_ptr的功能和操作都非常类似,如何在他们之间选取取决于是否需要转移所管理的对象的所有权(如是否需要作为函数的返回值)。如果没有这个需要的话,大可以使用boost::scoped_ptr,让编译器来进行更严格的检查,来发现一些不正确的赋值操作。
三、shared_ptr<T>
1、示例
#include <iostream> #include <memory> using namespace std; class X { public: X() { cout << "X ..." << endl; } ~X() { cout << "~X ..." << endl; } }; int main(void) { cout << "Entering main ..." << endl; shared_ptr<X> p1(new X); cout << p1.use_count() << endl; shared_ptr<X> p2 = p1; //boost::shared_ptr<X> p3; //p3 = p1; cout << p2.use_count() << endl; p1.reset(); cout << p2.use_count() << endl; p2.reset(); cout << "Exiting main ..." << endl; return 0; }
图示上述程序的过程也就是:
#include <iostream> #include <memory> #include <vector> using namespace std; class X { public: X() { cout << "X ..." << endl; } ~X() { cout << "~X ..." << endl; } }; int main(void) { vector<auto_ptr<X> > v; auto_ptr<X> p(new X); //v.push_back(p); //Error:auto_ptr调用了=运算符,而push_back传入是const参数,因此不能调用push_back vector<shared_ptr<X> > v2; shared_ptr<X> p2(new X); v2.push_back(p2); cout << p2.use_count() << endl; return 0; }
3、总结一下:
和前面介绍的scoped_ptr相比,shared_ptr可以共享对象的所有权,因此其使用范围基本上没有什么限制(还是有一些需要遵循的使用规则,下文中介绍),自然也可以使用在stl的容器中。另外它还是线程安全的,这点在多线程程序中也非常重要。
shared_ptr并不是绝对安全,下面几条规则能使我们更加安全的使用shared_ptr:
void f(shared_ptr<int>, int); int g(); void ok() { shared_ptr<int> p(new int(2)); f(p, g()); } void bad() { f(shared_ptr<int>(new int(2)), g()); }
#include <iostream> #include <memory> using namespace std; class Parent; class Child; typedef shared_ptr<Parent> parent_ptr; typedef shared_ptr<Child> child_ptr; class Child { public: Child() { cout << "Child ..." << endl; } ~Child() { cout << "~Child ..." << endl; } parent_ptr parent_; }; class Parent { public: Parent() { cout << "Parent ..." << endl; } ~Parent() { cout << "~Parent ..." << endl; } child_ptr child_; }; int main(void) { parent_ptr parent(new Parent); //1 child_ptr child(new Child); //1 parent->child_ = child; //2 child->parent_ = parent; //2 //parent->child_.reset(); //Error:解决析构问题 return 0; }
强引用与弱引用:
(1)强引用,只要有一个引用存在,对象就不能释放
(2)弱引用,并不增加对象的引用计数(实际上是不增加use_count_, 会增加weak_count_);但它能知道对象是否存在
【1】如果存在,提升为shared_ptr(强引用)成功
【2】如果不存在,提升失败
(3)通过weak_ptr访问对象的成员的时候,要提升为shared_ptr
(4)对于上述的例子,只需要将Parent 类里面的成员定义改为如下,即可解决循环引用问题:
class Parent { public: weak_ptr<parent> child_; };
#include <iostream> #include <memory> using namespace std; class Parent; class Child; typedef shared_ptr<Parent> parent_ptr; typedef shared_ptr<Child> child_ptr; class Child { public: Child() { cout << "Child ..." << endl; } ~Child() { cout << "~Child ..." << endl; } parent_ptr parent_; }; class Parent { public: Parent() { cout << "Parent ..." << endl; } ~Parent() { cout << "~Parent ..." << endl; } weak_ptr<Child> child_; }; int main(void) { parent_ptr parent(new Parent); child_ptr child(new Child); parent->child_ = child; child->parent_ = parent; return 0; }
因为此例子涉及到循环引用,而且是类成员引用着另一个类,涉及到两种智能指针,跟踪起来难度很大,我也没什么心情像分析shared_ptr 一样画多个图来解释流程,这个例子需要解释的代码远远比shared_ptr 多,这里只是解释怎样使用,有兴趣的朋友自己去分析一下。
(5)下面再举个例子说明lock() 和 expired() 成员函数函数的用法:
【1】lock():提升为share_ptr
【2】expired():的功能等价于use_count()==0,但更快,表示被观测的资源(也就是shared_ptr的管理的资源)已经不复存在。
#include <iostream> #include <memory> using namespace std; class X { public: X() { cout << "X ..." << endl; } ~X() { cout << "~X ..." << endl; } void Fun() { cout << "Fun ..." << endl; } }; int main(void) { weak_ptr<X> p; shared_ptr<X> p3; { shared_ptr<X> p2(new X); cout << p2.use_count() << endl; p = p2; cout << p2.use_count() << endl; /*shared_ptr<X> */p3 = p.lock(); cout << p3.use_count() << endl; if (!p3) cout << "object is destroyed" << endl; else p3->Fun(); } /* shared_ptr<X> p4 = p.lock(); if (!p4) cout<<"object is destroyed"<<endl; else p4->Fun(); */ if (p.expired()) cout << "object is destroyed" << endl; else cout << "object is alived" << endl; return 0; }
从输出可以看出,当p = p2; 时并未增加use_count_,所以p2.use_count() 还是返回1,而从p 提升为 p3,增加了use_count_, p3.use_count() 返回2;出了大括号,p2 被析构,use_count_ 减为1,程序末尾结束,p3 被析构,use_count_ 减为0,X 就被析构了。
参考 :
C++ primer 第四版
Effective C++ 3rd
C++编程规范