我们先来解释一下什么叫智能指针?
智能指针是利用RAII(在对象的构造函数中执行资源的获取(指针的初始化),在析构函数中释放(delete 指针):这种技法把它称之为RAII(Resource Acquisition Is Initialization:资源获取即初始化))来管理资源。
其本质思想是:将堆对象的生存期用栈对象(智能指针)来管理。也就是当new一个堆对象的时候,立刻用智能指针来接管,具体做法是在构造函数中进行初始化(用一个指针指向堆对象),在析构函数调用delete来释放堆对象。由于智能指针本身是一个栈对象,它的作用域结束的时候会自动调用析构函数,从而调用delete释放了堆对象。
这样,堆对象就由智能指针(栈对象)管理了,不容易发生内存泄露或者是野指针。
在讲解Boost库的智能指针之前,我们先来说一下STL中的auto_ptr,他的使用根据Effective STL 上的条款有着很多的限制:
1. auto_ptr不能共享所有权
2. auto_ptr不能指向数组
3. auto_ptr不能作为容器的成员
4. 不能通过赋值操作来初始化auto_ptr
std::auto_ptr<int> p(new int(42)); //ok
std::auto_ptr<int> p=new int(42); //error
这是因为auto_ptr的构造函数被定义成为了explicit
5. 不要把auto_ptr放入容器
(一) scoped_ptr<T>
#include <boost/scoped_ptr.hpp> #include <iostream> using namespace std; class X { public: X() { cout << "X ..." << endl; } ~X() { cout << "~X ..." << endl; } }; int main(void) { cout << "Entering main ..." << endl; { boost::scoped_ptr<X> pp(new X); //boost::scoped_ptr<X> p2(pp); //Error:所有权不能转移 } cout << "Exiting main ..." << endl; return 0; }运行结果:
Entering main ...
X ..
~X ...
Exiting main ....
Scoped_ptr不能够被拷贝和赋值,但是可以交换。不能够被拷贝和赋值的原因是赋值运算符和拷贝构造函数都是private。
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;
(二)shared_ptr<T>#include <boost/shared_ptr.hpp> #include <iostream> using namespace std; class X { public: X() { cout << "X ..." << endl; } ~X() { cout << "~X ..." << endl; } }; int main(void) { cout << "Entering main ..." << endl; boost::shared_ptr<X> p1(new X); cout << p1.use_count() << endl; boost::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; }运行结果:
Entering main ...
X ...
1
2
1
~X ...
Exiting main ...
shared_ptr是使用引用计数实现的,当计数值为0的时候,说明原生指针没有任何指针对其进行管理,执行delete。计数器的变化很简单:当新增一个shared_ptr对原生指针进行管理时,计数值+1,减少一个shared_ptr对原生指针进行管理时,计数值-1.
上面提到auto_ptr不能放到vector中,但是shared_ptr是可以的。这是因为vector的push_back(const _Ty:参数是const)是不能修改的(没有特殊处理),但是shared_ptr重载的运算符类型就是const,所以shared_ptr是可以放在STL的容器vector中使用的。另外,由于他的自增是原子性的,也是线程安全的,这在多线程程序中是非常重要的。
boost::shared_ptr并不是绝对安全,下面几条规则能使我们更加安全的使用boost::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
(三)weak_ptr<T> weak_ptr<T>的引入主要是为了解决shared_ptr的循环引用的问题:
#include <boost/shared_ptr.hpp> #include <iostream> using namespace std; class Parent; class Child; typedef boost::shared_ptr<Parent> parent_ptr; typedef boost::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); child_ptr child(new Child); parent->child_ = child; child->parent_ = parent; return 0; }
它们的引用计数最后都是1,不能自动释放,并且此时这两个对象再无法访问到。这就引起了内存泄漏
这里我们使用weak_ptr进行问题解决。这里不再跟踪源码,仅是简单的介绍一下使用,有兴趣的朋友可以自己跟踪一下。
两个常用的功能函数:expired()用于检测所管理的对象是否已经释放;lock()用于获取所管理的对象的强引用智能指针。
对于上面的例子,只要将Parent类的成员换成如下,即可解决循环引用的问题:
class Parent { public: boost::weak_ptr<parent> child_; };
强引用与弱引用:
强引用,只要有一个引用存在,对象就不能释放
弱引用,并不增加对象的引用计数(实际上是不增加use_count_, 会增加weak_count_);但它能知道对象是否存在
通过weak_ptr访问对象的成员的时候,要提升为shared_ptr
如果存在,提升为shared_ptr(强引用)成功
如果不存在,提升失败
#include <boost/shared_ptr.hpp> #include <boost/weak_ptr.hpp> #include <boost/scoped_array.hpp> #include <boost/scoped_ptr.hpp> #include <iostream> using namespace std; class X { public: X() { cout << "X ..." << endl; } ~X() { cout << "~X ..." << endl; } void Fun() { cout << "Fun ..." << endl; } }; int main(void) { boost::weak_ptr<X> p; boost::shared_ptr<X> p3; { boost::shared_ptr<X> p2(new X); cout << p2.use_count() << endl; p = p2; cout << p2.use_count() << endl; /*boost::shared_ptr<X> */ p3 = p.lock(); cout << p3.use_count() << endl; if (!p3) cout << "object is destroyed" << endl; else p3->Fun(); } /*boost::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; }
X ...
1
1
2
Fun ...
Object is alived
~X ...
从输出可以看出,当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 就被析构了。
(一)explicit
explicit关键字用来修饰类的构造函数,被修饰的构造函数的类,不能发生相应的隐式类型转换,只能以显示的方式进行类型转换。
Circle A=1.23;
实际上进行了以下操作:
Tmp=Cirlce(1.23);
Circle A(tmp);
Tmp.~Circle();
隐式调用了拷贝构造函数,当构造函数都加上explicit关键字后,上面这些都是错误的。
只能是Circle A(1.23);(double形参)
Circle C(A);(拷贝构造函数)
(二)const
const常量并非完全不能改变,例如:const int n=100;
p=&n;
*p=200;
通过指针来修改是可以的,这是一种技巧。