注意:现在boot库已经归入STL库,用法基本上还和boost类似
在C++11中,引入了智能指针。主要有:unique_ptr, shared_ptr, weak_ptr。
这3种指针组件就是采用了boost里的智能指针方案。很多有用过boost智能指针的朋友,很容易地就能发现它们之间的关间:
std | boost | 功能说明 |
---|---|---|
unique_ptr | scoped_ptr | 独占指针对象,并保证指针所指对象生命周期与其一致 |
shared_ptr | shared_ptr |
可共享指针对象,可以赋值给shared_ptr或weak_ptr。
指针所指对象在所有的相关联的shared_ptr生命周期结束时结束,是强引用。 |
weak_ptr | weak_ptr | 它不能决定所指对象的生命周期,引用所指对象时,需要lock()成shared_ptr才能使用。 |
C++11将boost里的这一套纳入了标准。
一、boost 智能指针
智能指针是利用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; }
图示上述程序的过程也就是:
再深入一点,可以看源码,但shared_ptr 的实现 比 scoped_ptr 要复杂许多,涉及到多个类。
2、shared_ptr<T>与auto_ptr<T>
(1)之前我们讲过auto_ptr<T>不能放置vector中
(2)但share_ptr<T>可以放置vector中
#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:
1. 避免对shared_ptr所管理的对象的直接内存管理操作,以免造成该对象的重释放
2. shared_ptr并不能对循环引用的对象内存自动管理(这点是其它各种引用计数管理内存方式的通病)。
3. 不要构造一个临时的shared_ptr作为函数的参数。
详见 http://www.boost.org/doc/libs/1_52_0/libs/smart_ptr/shared_ptr.htm
如下列bad 函数内 的代码则可能导致内存泄漏:
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()); }
如bad 函数内,假设先构造了堆对象,接着执行g(), 在g 函数内抛出了异常,那么由于裸指针还没有被智能指针接管,就会出现内存泄漏。
四、weak_ptr<T>
1、shared_ptr<T>的缺点:循环问题
如上总结shared_ptr<T> 时说到引用计数是一种便利的内存管理机制,但它有一个很大的缺点,那就是不能管理循环引用的对象。
#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; }
运行结果:
问题:没有调用析构函数
如上述程序的例子,运行程序可以发现Child 和 Parent 构造函数各被调用一次,但析构函数都没有被调用。由于Parent和Child对象互相引用,
它们的引用计数最后都是1,不能自动释放,并且此时这两个对象再无法访问到。这就引起了内存泄漏。
其中一种解决循环引用问题的办法是 手动打破循环引用,如在return 0; 之前加上一句 parent->child_.reset(); 此时
当栈上智能指针对象child 析构,Child 对象引用计数为0,析构Chlid 对象,它的成员parent_ 被析构,则Parent 对象引用计数减为1,故当栈上智能指针对象parent
析构时,Parent 对象引用计数为0,被析构。
2、弱引用智能指针 weak_ptr<T>
但手动释放不仅麻烦而且容易出错,这里主要介绍一下弱引用智能指针 weak_ptr<T> 的用法。
强引用与弱引用:
(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++编程规范