一个智能指针的实现(改进)
单承亮 (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 
|