从这个条款开始,就进入到资源管理部分了。资源管理往往是大型项目的一个难点,也是重中之重,看到一些编程规范,都是将资源管理的规范列为高优先级的重点。
管理资源的最好方法其实是预防,而好的预防方法就是尽量不去使用C/C++的原生指针,这些指针像幽灵一样,一个“忘记”,就是一个隐患。当项目小的时候,这些隐患看不出来,但当研发一个拥有上万级用户的产品时,服务器对很多人同时运行含有隐患的代码,这个隐患就会爆发,导致内存不足而崩溃。
举个例子,初学者常常这样写:
1 int *p = new int(); 2 … 3 delete p;
这个当然是OK的,内存可以回收,但万一中间的过程超过数十行,你还能记住去delete p吗?
有些同学可能会这样做,每一个new之后,先写好delete,再往中间插代码,但如果遇到这样的情况:
1 int* GetResource() 2 { 3 return new int(10); 4 }
这个函数负责生成资源,并将之返回给上一层,因为这个资源是要在上一层用的,所以在函数内还不能直接delete,这可就麻烦了。程序员们可要随时记挂着这个内存,在不再需要的时候赶紧释放了。但一切可能出错的地方真的会出错,代码一多,记性就不那么好了,很容易出现忘记delete的事情。
那么有没有什么好的方法,可以让我们痛快地管理资源呢?这就牵涉到智能指针的问题了。常用的智能指针有memory头文件里面的auto_ptr,还有boost库里的shared_ptr。两者的实现机制不同,但功能是类似的,就是能自动管理资源。他们体现的思想就是RAII(Resources Acquisition Is Initialzeation 资源取得的时机便是初始化时机)。
举个形象的例子就是:
auto_ptr<int> ap(new int(10));
或者是:
shared_ptr<int> sp(new int(10));
可以看到,这里根本就没有出现原生指针,而是直接将new出来的内存交给了一个“管理者”。这个管理者可以是auto_ptr,也可以是shared_ptr,他们的存在,使得程序员可以真的忘记delete了,当ap或者sp的生命周期结束时,他们会将资源释放出来,交还给系统。比如:
1 void fun() 2 { 3 auto_ptr<int> ap(new int(10); 4 *ap = 20; 5 } 6 // ap 在fun()结束后会自动回收掉
auto_ptr和shared_ptr的区别在于管理方法不同。
auto_ptr这个管理者是一个很霸气的boss,他只想独管资源,而不允许其他管理者插手,否则他就退出管理,把这个资源交给另一方,自己再也不碰了。
举个例子:
auto_ptr<int> boss1(new int(10)); // 这个时候boss1对这个资源进行管理
auto_ptr<int> boss2(boss1); // 这个时候boss2要插手进来,boss1很生气,所以把管理权扔给了boss2,自己就不管事了。所以boss2持有这个资源,而boss1不再持有(持有的是null)。
所以同一时刻,只有一个auto_ptr能管理相同的资源。
这里还是有必要解释一下这句话的,比如:
1 auto_ptr<int> boss1(new int(10)); 2 auto_ptr<int> boss2(new int(10));
boss1还持有资源吗?答案是有的,因为new执行了两次,虽然内存空间里的初始值是一样的,但地址并不同,所以不算相同的资源。
再来,为了把问题讲清楚,这里就让原生指针再出场一下:
1 int *p = new int(10); 2 auto_ptr<int> boss1(p); 3 auto_ptr<int> boss2(p);
boss1还持有资源吗?
答案是当程序运行到第三句的时候,就弹出assertion failed的红叉,程序崩溃了,为什么会这样呢?因为p所指向的是同一块资源,所以boss2发出管理手段后,boss1肯定要有动作的。第三句话调用boss2的构造函数,但构造函数中boss2识别出来p所指向的资源已经被占用了,所以会assertion failed。
事实上,这种原生指针和智能指针混用的程序是非常不好的,如果不使用原生指针,上面的崩溃问题就不会发生。
我自己根据auto_ptr的思想,仿写了一下这个类,有兴趣的读者可以看看,其实并不复杂:
1 #ifndef MY_AUTO_PTR_H 2 #define MY_AUTO_PTR_H 3 4 // 自定义的智能指针 5 #include <iostream> 6 using namespace std; 7 8 template <class T> 9 class MyAutoPtr 10 { 11 private: 12 T *ptr; 13 static void swap(MyAutoPtr &obj1, MyAutoPtr &obj2) 14 { 15 // 细到交换成员变量 16 std::swap(obj1.ptr, obj2.ptr); 17 }; 18 19 public: 20 explicit MyAutoPtr(T *p = NULL): ptr(p){} 21 22 MyAutoPtr(MyAutoPtr& p) 23 { 24 ptr = p.ptr; 25 p.ptr = 0; 26 } 27 28 MyAutoPtr& operator= (MyAutoPtr& p) 29 { 30 if(&p != this) 31 { 32 MyAutoPtr temp(p); 33 swap(*this, temp); 34 } 35 return *this; 36 } 37 38 ~MyAutoPtr() 39 { 40 delete ptr; 41 ptr = 0; 42 } 43 44 T& operator* () const 45 { 46 return *ptr; 47 } 48 49 T* operator-> () const 50 { 51 return ptr; 52 } 53 54 T* get() const 55 { 56 return ptr; 57 } 58 59 60 friend ostream& operator<< (ostream& out, const MyAutoPtr<T>& obj) 61 { 62 out << *obj.ptr; 63 return out; 64 } 65 66 }; 67 68 69 #endif /* MY_AUTO_PTR_H */