一个智能指针的实现(改进)

类别:编程语言 点击:0 评论:0 推荐:

一个智能指针的实现(改进)

                                   单承亮 (Simouse)      2004-8-22

   前些日子写了一个shared_ptr,后来在应用中发现了些安全漏洞,这篇文档是对shared_ptr改进后的使用和注意事项的一个简要说明,希望广大C++开发者给予意见和改进。让我们在复杂的系统中杜绝内存泄漏。这次改进并没有增加什么功能,而是删除了几个存在安全问题的操作,如release操作。有关和STL里的auto_ptr之间的转换我也做了一些测试,目前只提供auto_ptr到shared_ptr的单向转换,因为auto_ptr只有一个拥有权,所以我们无法确定最终是由谁释放内存。我还是希望从头对这个类做一个新的说明,希望是对以前疏忽的一个弥补。
Template class shared_data
 shared_data是管理指针并计数的类,是多个shared_ptr对象共同管理的一个私有对象,多个shared_ptr对象以共有同一个shared_data对象来实现管理指针的计数与释放。
 class shared_data
 {
  friend class shared_ptr<T>;
 private:
  explicit shared_data(T* pT) ;
  ~shared_data() ;
  void plus();   // 计数加1
  void minus();   // 计数减2
  T*  get();   // 返回T类型指针
  const T*  get() const ; // 为了处理const对象

 private:
  T* _M_ptr;   // 管理的T类型指针
  unsigned int _M_nCount; // 计数器
 };
 shared_ptr是shared_data友员类,所以只有shared_ptr对象才能访问shared_data对象,把shared_data的内容写成private还是有好处的,比以前写那个shared_data还是精简了些。
成员说明:
T* _M_ptr;
  这是我们分配出来的对象T的指针,也是我们管理的对象。
unsigned int _M_nCount;
  这是我们的计数变量,初始时是1,构造新对象时加1,析构时减1,
当为0时又minus释放资源,包括被管理的T对象和共有的shared_data 对象。
T* get();和const T* get() const;是为了兼容const对象
Template class shared_ptr
 shared_ptr是用来操作shared_data对象并提供给我们操作T对象接口,所有同类型的shared_ptr可以有多可实例,但只有一个所共享的管理类shared_data,当shared_ptr对象做Copy或Assignment操作时会对当前管理的(如果有的话)shared_data对像做minus操作,实现计数减1 ,关于是否释放的问题由minus决定;然后得到新的shared_data对象并对它做plus操作,实现计数加1。
 class shared_ptr
 {
  typedef shared_data<T> element;
 public:
  explicit shared_ptr(T* pT);
  explicit shared_ptr();
 shared_ptr(const shared_ptr<T>& rT);
  ~shared_ptr();
 const shared_ptr<T>& operator = (const shared_ptr<T>& rT) ;
  T& operator* ();
  const T& operator* () const;
  T* operator-> ();
  const T* operator-> () const ;
  bool operator== (const shared_ptr<T>& rT) const;
  bool operator== (const T* pT) const;
bool operator!= (const T* pT) const ;
  T* get() ;
  const T* get() const;
  void reset(T* pT);
 private:
  element* get_element() const;
  element* _M_pD;
 };
数据区
成员_M_pD,是shared_data对象的指针,是多个shared_ptr实例共同管理的对象, shared_ptr只负责shared_data的分配,不负责释放,其它操作就是对shared_data计数做plus和minus操作,内存的释放又minus操作激发。

公用成员函数
构造函数加一explicit是为了避免有如这样的操作:shared_ptr<Ctest> ptrT = &t;在默认构造里我们不分配shared_data对象。当传进来NULL我们也不分配shared_data对象。值得一说的是做Assignment操作时一定要对现有的shared_data对象做minus操作。析构只对shared_data做minus操作。
Operator ->, *, ==和get都提供了两个版本,主要是兼容const对象,有一个共同点就是都是通过shard_data的get获得T对象的指针。
如果要重置shared_ptr的话reset是唯一的方法,我们可以传个NULL进去来置空shared_ptr对象,也可以重新分配新的管理对象,也可以从现有的shared_ptr对象重置,不过这有点像Assignment操作。

注:关于删除release操作是为了管理对象释放的唯一性,shared_ptr与auto_ptr接口基本相同,但由于shared_ptr是允许多个实例,最终释放都是不确定的,所以我们应尽量避免自己释放资源,一切由shared_ptr来管理。
Test
我们先定义一个简单的测试类
class CTest
{
public:
 CTest(char* lpszName){
  m_lpszName = lpszName;
  cout<<"CTest() : " <<m_lpszName <<endl;
 }
 void Set(char* lpszName){
  m_lpszName = lpszName;
 }
 ~CTest(){
  cout<<"~CTest() : " <<m_lpszName <<endl;
 }
 void Print() const{
  cout<< "Print() : " <<m_lpszName <<endl;
 }
private:
 char* m_lpszName;
};

下面我们着重看下测试问题:
void main()
{
 // Test constructor
 // 这是我们标准定义shared_ptr对象的方法
 shared_ptr<CTest> t1(new CTest("t1"));


 // Test copy constructor
 // 当然有现成的这样最好
 shared_ptr<CTest> t2(t1);
 
 // Test assignment operator
 shared_ptr<CTest> t3 = t1;
 
 // 在做->和*操作前最好判断下shared_ptr对象是不为NULL
 
 if (t3 != NULL){ // 记住不能是if (NULL != t3),因为没有写这个操作
      // 也不用怕写成if(t3 = NULL),因为也没写这个操作(:
  // do some operator
}

 // Test operator->
 cout <<"\nTest operator->" <<endl;
 cout <<"t1->Print()\t";
 t1->Print(); 
 cout <<"t2->Print()\t";
 t2->Print(); 
 cout <<"t3->Print()\t";
 t3->Print(); 
 
 // Test operator*
 cout <<"\nTest operator*" <<endl;
 cout <<"(*t1).Print()\t";
 (*t1).Print(); 
 cout <<"(*t2).Print()\t";
 (*t2).Print();  
 cout <<"(*t3).Print()\t";
 (*t3).Print(); 

 // Test get
 // 当你要用T对象指针时也可以把它取出来,不过不要做delete,要考虑shared_ptr的
// 有效期
 cout <<"\nTest get\t" <<endl;
 cout <<"t1.get()->Print()\t";
 t1.get()->Print();
 
 // Test operator==
 cout <<"\nTest operator==" <<endl;
 cout <<"t1 == t2\t" <<(t1 == t2) <<endl;
 cout <<"t1 == t2.get()\t" <<(t1 == t2.get()) <<endl;
 
 // Test reset
 cout <<"\nTest reset" <<endl;
 // reset 和 assignment最大的区别是它能分配新的对象
 cout <<"t2.reset(new CTest(\"t2\"))\t" <<endl;
 t2.reset(new CTest("t2"));
 cout <<"t3.reset(new CTest(\"t3\"))\t" <<endl;
 t3.reset(t2);

 // Test const type
 // 这些操作都是不允许的,那我们也阻止这种事儿的发生
 const shared_ptr<CTest> t4(new CTest("t4"));
 t4.get()->Print();
 //t4->Set("Hello");  // Error
 //(*t4).Set("Hello"); // Error
 //t4 = t3;    // Error
 //t4.release();   // Error
 //t4.reset(NULL);  // Error

 // Test compatible for auto_ptr
 // 值得一提的是我们可以很轻松的从auto_ptr转到shared_ptr,不过不是往返而是单向
// 为什么?你想让auto_ptr和shared_ptr同时释放同一块内存吗?如果你只有一个
// shared_ptr拥有这块内存是可以的,你能保证吗?这就是为什么我写shared_ptr的目的。
 auto_ptr<CTest> t5(new CTest("t5"));
 shared_ptr<CTest> t6;
 t6.reset(t5.release());
 t6->Print();
 if (NULL == t5.get()){
  cout <<"t5 = NULL" <<endl;
 }
 // are you sure do it?
 t5.reset(t6.get());
 // 这时你知道是auto_ptr释放CTest还是shared_ptr释放?所以不要这么做!

 cout <<"\nTest End!\n" <<endl;
}

由于水平有限,可能有的地方存在问题,欢迎大家加于改善,在此给出全部代码,供大家参考
File: shared_ptr.h
//////////////////////////////////////////////////////////////////////////
//
// Module Name :
//
//  Template class shared_ptr
//
// Author:
//
//  Chengliang Shan         2004-08-22
//
//  [email protected]
//
//////////////////////////////////////////////////////////////////////////
#ifndef SHARED_PTR_H_
#define SHARED_PTR_H_

namespace clshan {
 
 template<class T>
 class shared_ptr;

 template<class T>
 class shared_data
 {
  friend class shared_ptr<T>;
 private:
  explicit shared_data(T* pT):_M_ptr(NULL), _M_nCount(0) {
   if (NULL != pT) {
    _M_ptr = pT;
    _M_nCount = 1;
   }
  }

  ~shared_data() {
  }
  
  void plus() {
   ++_M_nCount;
  }
  
  void minus() {
   --_M_nCount;
   if (0 == _M_nCount) {
    delete _M_ptr;
    delete this;
   }
  }

  T*  get() {
   return _M_ptr;
  }

  const T*  get() const {
   return _M_ptr;
  }

 private:
  T* _M_ptr;
  unsigned int _M_nCount;
 };

 template<class T>
 class shared_ptr
 {
  typedef shared_data<T> element;
 public:
  explicit shared_ptr(T* pT):_M_pD(NULL) {
   if (NULL != pT) {
    _M_pD = new element(pT);
   }   
  }

  explicit shared_ptr():_M_pD(NULL){
  }

  // copy constructor
  shared_ptr(const shared_ptr<T>& rT) {
   _M_pD = rT.get_element();
   if (NULL != _M_pD) {
    _M_pD->plus();
   }
  }

  ~shared_ptr() {
   if (NULL != _M_pD) {
    _M_pD->minus();
   }
  }

  // assignment operator
  const shared_ptr<T>& operator = (const shared_ptr<T>& rT) {
   if (NULL != _M_pD) {
    _M_pD->minus();
   }

   _M_pD = rT.get_element();
   if (NULL != _M_pD) {
    _M_pD->plus();
   }
   return *this;
  }

  T& operator* ()  {
   return *_M_pD->get();
  }

  const T& operator* () const {
   return *_M_pD->get();
  }  

  T* operator-> ()  {
   return _M_pD->get();
  }

  const T* operator-> () const {
   return _M_pD->get();
  }
  

  bool operator== (const shared_ptr<T>& rT) const {
   if (NULL != _M_pD) {
    return _M_pD->get() == rT.get();
   }

   return NULL == rT.get();
  }
  
  bool operator== (const T* pT) const {
   if (NULL != _M_pD) {
    return _M_pD->get() == pT;
   }

   return NULL == pT;
  }

  bool operator!= (const T* pT) const {
   if (NULL != _M_pD) {
    return _M_pD->get() != pT;
   }
   
   return NULL != pT;
  }
  
  T* get() {
   if (NULL != _M_pD) {
    return _M_pD->get();
   }
   return NULL;
  }

  const T* get() const {
   if (NULL != _M_pD) {
    return _M_pD->get();
   }   
   return NULL;
  }
  
  void reset(T* pT) {
   if (NULL != _M_pD) {
    _M_pD->minus();
    _M_pD = NULL;
   }
   if (NULL != pT) {
    _M_pD = new element(pT);
   }   
  }

  void reset(const shared_ptr<T>& rT) {
   if (NULL != _M_pD) {
    _M_pD->minus();
    _M_pD = NULL;
   }
   if (NULL != rT.get()) {
    _M_pD = rT.get_element();
    _M_pD->plus();
   }   
  }

 private:
  element* get_element() const {
   return _M_pD;
  }

  element* _M_pD;
 };
}
#endif

http://simouse.51.net/shared_ptr.doc
http://simouse.51.net/soft/shared_ptr.h

本文地址:http://com.8s8s.com/it/it28488.htm