【自己动手】实现简单的C++ smart pointer

转自 http://blog.csdn.net/ruizeng88/article/details/6691191


Why Smart Pointer?

为什么需要智能指针?因为c++的内存管理一直是个令人头疼的问题。

假如我们有如下person对象:每个person有自己的名字,并且可以告诉大家他叫什么名字:

[cpp]  view plain copy
  1. //  
  2. //a person who can tell us his/her name.  
  3. //  
  4. #include<iostream>  
  5. #include<string>  
  6. using namespace std;  
  7. class person{  
  8. public:  
  9.     person(string);  
  10.     void tell();  
  11.     ~person();  
  12. private:  
  13.     string name;        
  14. };  
  15.   
  16. person::person(string name):name(name){  
  17.   
  18. }  
  19.   
  20. void person::tell(){  
  21.     cout << "Hi! I am " << name << endl;  
  22. }  
  23.   
  24. person::~person(){  
  25.     cout << "Bye!" << endl;  
  26. }  
很多时候,我们并不知道自己要创建多少个对象,因此需要在程序运行中动态地创建和销毁对象- -这时我们会使用new操作符在堆(heap)中为创建的对象分配内存,使用delete释放分配的内存。一个简单的示例:

[cpp]  view plain copy
  1. #include "person.h"  
  2. int main(){  
  3.     person *p = new person("Cici");  
  4.     p -> tell();  
  5.     delete p;  
  6. }  
程序的执行结果:

简单的程序我们当然不会忘记释放堆中分配的对象。但是当程序复杂时,成千上万对象被动态的创建,而在不使用这些对象时,都需要及时的释放,否则就会造成内存泄露(memory leak)。内存泄露是c++程序中最常见的问题之一,因为c++本身并没有提供自动堆内存管理的功能,所以,所有这些任务都交给了程序员自己--程序员必须对自己动态创建的堆对象负责:在对象不需要被使用时及时地销毁对象。然而,程序员不是万能的,所以内存泄露,总是成为c++程序的常见bug。

这里不得不提到的是java,java增加了垃圾回收机制(garbage collection),jvm会自己管理那些不被使用到的对象并及时销毁他们,这大大减轻了程序员的负担,程序员只需要new自己需要的对象而不必去释放他们,因为,在对象不被使用(被引用)时,垃圾回收器会替我们清理残骸。java也因为这一特性而广受关注。

STL auto_ptr

c++程序员们当然也不甘寂寞,stl中有了“智能指针”:stl::auto_ptr。使用智能指针,我们就可以不必关心对象的释放,因为智能指针会帮助我们完成对象内存空间的释放。有了auto_ptr,我们的程序可以这样写了:

[cpp]  view plain copy
  1. #include "person.h"  
  2. #include<memory>  
  3. using namespace std;  
  4. int main(){  
  5.     auto_ptr<person> p(new person("Cici"));  
  6.     p -> tell();  
  7.     //we don't have to delete p because smart pointer will handle this  
  8.     //delete p;  
  9. }  
执行程序,输出如下:

可以看到输出和我们的第一个版本一模一样。虽然我们并没有delete我们创建的对象,但是可以看到,它已经在程序退出之前被正确的析构了。

简单的smart_ptr
通过上面的示例,看到stl的auto_ptr的用法,于是我们可以先总结下一个应该具备的基本功能:
1,对所指向的对象,能够自动释放
2,重载了“->”操作符,我们在使用smart_ptr时,能够像普通指针一样使用“->”访问其所指向的对象的成员
3,重载了“*”解引用操作符,同上
那么,如何实现对象的自动释放呢?
我们知道,对于局部对象,其生存周期是对象所在的局部作用域(一般是程序中的“{ }”之间),而在程序跑出局部对象的作用域之后,这些局部对象就会被自动销毁(这时对象的析构函数也会被调用)。所以我们的smart_ptr可以在其析构函数中显式delete所指向的对象,这样我们指向的对象也会在smart_ptr作用域之外被释放。所以我们可以这样实现我们自己的smart_ptr:
[cpp]  view plain copy
  1. //  
  2. //our simple smart pointer  
  3. //  
  4. #include "person.h"  
  5. class smart_ptr{  
  6. public:  
  7.     smart_ptr(person* p);  
  8.     ~smart_ptr();  
  9.     person& operator*();  
  10.     person* operator->();  
  11. private:  
  12.     person *ptr;  
  13. };  
  14.   
  15. smart_ptr::smart_ptr(person* p):ptr(p){  
  16.       
  17. }  
  18.   
  19. smart_ptr::~smart_ptr(){  
  20.     delete ptr;  
  21. }  
  22.   
  23. person&  smart_ptr::operator*(){  
  24.     return *ptr;  
  25. }  
  26.   
  27. person* smart_ptr::operator->(){  
  28.     return ptr;  
  29. }  
来测试一下这个简单的smart_ptr:
[cpp]  view plain copy
  1. #include "smart_ptr.h"  
  2. using namespace std;  
  3. int main(){  
  4.     smart_ptr p(new person("Cici"));  
  5.     p -> tell();  
  6.     //we don't have to delete p because smart pointer will handle this  
  7.     //delete p;  
  8. }  
运行结果:

哈哈,我们的smart_ptr正常工作了。
但是可以看到,这样的smart_ptr有很大的缺点,我们的智能指针只能指向我们的person对象,我们需要他指向新的对象时岂不是又要依葫芦画瓢写一个新的smart_ptr么?显然,这是c++,我们当然有更通用的方法:模板。我们可以使用模板使我们的smart_ptr在编译时决定它指向的对象,所以改进的版本:
[cpp]  view plain copy
  1. //  
  2. //our simple smart pointer  
  3. //  
  4. template <typename T>  
  5. class smart_ptr{  
  6. public:  
  7.     smart_ptr(T* p);  
  8.     ~smart_ptr();  
  9.     T& operator*();  
  10.     T* operator->();  
  11. private:  
  12.     T* ptr;  
  13. };  
  14.   
  15. template <typename T>  
  16. smart_ptr<T>::smart_ptr(T* p):ptr(p){  
  17.       
  18. }  
  19.   
  20. template <typename T>  
  21. smart_ptr<T>::~smart_ptr(){  
  22.     delete ptr;  
  23. }  
  24.   
  25. template <typename T>  
  26. T&  smart_ptr<T>::operator*(){  
  27.     return *ptr;  
  28. }  
  29.   
  30. template <typename T>  
  31. T* smart_ptr<T>::operator->(){  
  32.     return ptr;  
  33. }  

引用计数

我们的smart_ptr是不是完美了呢?看看下面这种情况:

[cpp]  view plain copy
  1. #include "person.h"  
  2. #include "smart_ptr.h"  
  3. using namespace std;  
  4. int main(){  
  5.     smart_ptr<person> p(new person("Cici"));  
  6.     p -> tell();  
  7.     {  
  8.         smart_ptr<person> q = p;  
  9.         q -> tell();  
  10.     }  
  11. }  
执行一下:

程序出错了。分析一下很容易找到原因:我们的智能指针q在程序跑出自己的作用域后就释放了指向的person对象,而我们的指针p由于和q指向的同一对象,所以在程序退出时企图再次释放一个已经被释放的对象,显然会出现经典的segmentation fault异常了。所以,我们“简单的”smart_ptr是过于“简单”了。

有什么办法解决这个问题呢?想想操作系统中的文件引用计数器。操作系统为每个打开的文件维护一个“引用计数”,当多个进程同时打开一个文件时系统会依次将引用计数加1。若某个进程关闭了某个文件此时文件不会立即被关闭,系统只是将引用计数减1,当引用计数为0时表示这时已经没用人使用这个文件了,系统才会把文件资源释放。所以这里,我们也可以为smart_ptr所指向的对象维护一个引用计数,当有新的smart_ptr指向这个对象时我们将引用计数加1,smart_ptr被释放时我们只是将引用计数减一;当引用计数减为0时,我们才真正的销毁smart_ptr所指向的对象。

另外,我们之前的smart_ptr还缺少无参构造函数,拷贝构造函数和对“=”运算符的重载。我们之前的版本并没有重载“=”运算符但程序依然可以正常执行,这是因为此时使用了编译器的合成版本,这也是不安全的(尽管这里没有发现问题)。

好吧,下面是最终的修改版本(添加的部分我特意都添加了注释):

[cpp]  view plain copy
  1. //  
  2. //smart_ptr.h : our simple smart pointer  
  3. //  
  4. template <typename T>  
  5. class smart_ptr{  
  6. public:  
  7.     //add a default constructor  
  8.     smart_ptr();  
  9.     //  
  10.     smart_ptr(T* p);  
  11.     ~smart_ptr();  
  12.     T& operator*();  
  13.     T* operator->();  
  14.     //add assignment operator and copy constructor  
  15.     smart_ptr(const smart_ptr<T>& sp);  
  16.     smart_ptr<T>& operator=(const smart_ptr<T>& sp);  
  17.     //  
  18. private:  
  19.     T* ptr;  
  20.     //add a pointer which points to our object's referenct counter  
  21.     int* ref_cnt;  
  22.     //  
  23. };  
  24.   
  25. template <typename T>  
  26. smart_ptr<T>::smart_ptr():ptr(0),ref_cnt(0){  
  27.     //create a ref_cnt here though we don't have any object to point to  
  28.     ref_cnt = new int(0);  
  29.     (*ref_cnt)++;  
  30. }  
  31.   
  32. template <typename T>  
  33. smart_ptr<T>::smart_ptr(T* p):ptr(p){  
  34.     //we create a reference counter in heap  
  35.     ref_cnt = new int(0);  
  36.     (*ref_cnt)++;  
  37. }  
  38.   
  39. template <typename T>  
  40. smart_ptr<T>::~smart_ptr(){  
  41.     //delete only if our ref count is 0  
  42.     if(--(*ref_cnt) == 0){  
  43.         delete ref_cnt;  
  44.         delete ptr;  
  45.     }  
  46. }  
  47.   
  48. template <typename T>  
  49. T&  smart_ptr<T>::operator*(){  
  50.     return *ptr;  
  51. }  
  52.   
  53. template <typename T>  
  54. T* smart_ptr<T>::operator->(){  
  55.     return ptr;  
  56. }  
  57.   
  58. template <typename T>  
  59. smart_ptr<T>::smart_ptr(const smart_ptr<T>& sp):ptr(sp.ptr),ref_cnt(sp.ref_cnt){  
  60.     (*ref_cnt)++;  
  61. }  
  62.   
  63. template <typename T>  
  64. smart_ptr<T>& smart_ptr<T>::operator=(const smart_ptr<T>& sp){  
  65.     if(&sp != this){  
  66.         //we shouldn't forget to handle the ref_cnt our smart_ptr previously pointed to  
  67.         if(--(*ref_cnt) == 0){  
  68.             delete ref_cnt;  
  69.             delete ptr;  
  70.         }  
  71.         //copy the ptr and ref_cnt and increment the ref_cnt  
  72.         ptr = sp.ptr;  
  73.         ref_cnt = sp. ref_cnt;  
  74.         (*ref_cnt)++;  
  75.     }  
  76.     return *this;  
  77. }  
为了测试我们的smart_ptr的全部功能,这里测试用例也做一些增加:

[cpp]  view plain copy
  1. #include "person.h"  
  2. #include "smart_ptr.h"  
  3. using namespace std;  
  4. int main(){  
  5.     smart_ptr<person> r;  
  6.     smart_ptr<person> p(new person("Cici"));  
  7.     p -> tell();  
  8.     {  
  9.         smart_ptr<person> q = p;  
  10.         q -> tell();  
  11.         r = q;  
  12.         smart_ptr<person> s(r);  
  13.         s -> tell();  
  14.     }  
  15.     r -> tell();  
  16. }  
执行结果如下:

可以看到,我们的Cici对象只有在最后才被销毁,我们的smart_ptr终于顺利完成任务了。

ps:

写到这里,我们的smart_ptr是否完美了呢?No。

比如:我们的对象如果被多个线程中的smart_ptr引用,我们的smart_ptr就又有出问题的隐患了,因为这里根本没有考虑线程对ref_cnt访问的互斥,所以我们的引用计数是有可能出现计数问题的。这里就不再实现了,毕竟,我们这里的smart_ptr只是是“简单的”实现~




//main.cpp

#include "person.h"
#include "smart_ptr.h"

using namespace std;
int test() {
  //auto_ptr<person> p(new person("Cici"));
  //SmartPointer<person> p(new person("Cici"));
  //p -> tell();
  SmartPointer<person> r(new person("taoqi"));  
  SmartPointer<person> p(new person("Cici"));  
  p -> tell();  
  {  
	SmartPointer<person> q = p;  
    q -> tell();  
    r = q;  
	SmartPointer<person> s(r);  
    s -> tell();  
  }  
  r -> tell();  
  return 0;

}
int main(){
  test();
  return 0;
  
}

//smart_ptr.h

template <class T> 
class SmartPointer {
 public:

  SmartPointer(T* ptr) {
    ref = ptr;
    ref_count = (unsigned*)malloc(sizeof(unsigned));
    *ref_count = 1;
  }
  SmartPointer(SmartPointer<T> & sptr) {
    ref = sptr.ref;
    ref_count = sptr.ref_count;
    ++*ref_count;
  }
  T* SmartPointer<T>::operator->() {  
    return ref;  
  }  
  T& SmartPointer<T>::operator*() {  
    return *ref;  
  }  
  ~SmartPointer() {
    --*ref_count;
    if (*ref_count == 0) {
      delete ref;
      free(ref_count);
      ref = NULL;
      ref_count = NULL;
    }
  }
  SmartPointer<T> & operator=(SmartPointer<T> & sptr) {
    if (this != &sptr) {
      ref = sptr.ref;
      ref_count = sptr.ref_count;
      ++*ref_count;
    }
    return *this;
  }
  T getValue() { 
    return *ref; 
  }
 protected:
  T * ref;
  unsigned * ref_count;
};

基本的函数:构造函数、析构函数、拷贝构造函数

重载运算符的函数:T* operator->()     T& operator*()     SmartPointer<T> & operator=(SmartPointer<T>& sptr) T getValue()

计数器增加的场合:拷贝构造函数,重载=的操作

计数器减少的场合:析构函数,即离开作用域的场合 



//person.h

//
//a person who can tell us his/her name.
//
#include<iostream>
#include<string>
using namespace std;
class person{
public:
	person(string);
	void tell();
	~person();
private:
	string name;	  
};
person::person(string name):name(name){
}
void person::tell(){
  cout << "Hi! I am " << name << endl;
}
person::~person(){
  cout << "Bye!" << endl;
}












  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值