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