(1) 如果释放的太早,那么其它的指针仍然指向这片内存,如果再使用它会造成未定义行为。
(2) 如果一直不释放可能会丢失最后一个指向这个对象的指针 导致内存无法被释放。
用 C++ 的方法来解决这种问题就是建立一个类来包含需要管理的指针 ,由于这些类往往与被管理者相绑定 ,所以它们被称为 handel 类 ,人们再建立这种 handel 类的同时一般保留了它包含的指针的种种特性,所以也称这种类为 智能指针 smart pointer。
最简单的 handel
这种 handel 只是一个包含了对象指针的容器,当对象的指针绑定到 handel 上后 ,就不需要手动delete 对象 ,handel 类负责对象的析构(在 handel 离开作用域时)。stl 中 的 auto_ptr 就是这种例子。
下面给出一个简单的 AutoPtr 实现:
/*
* File : auto_prt.h
* Discription : 智能指针
* 指针存储的最简单策略 , 将指针存入对象中当对象被析构指针自动被delete
* AutoPtr 和 指针是 一对一的关系
* Usage : AutoPtr< ClassType > ap_type = AutoPtr< ClassType >( new ClassType() );
* ap_type->method();
*/
#ifndef _PATTERN_AUTOPTR_H
#define _PATTERN_AUTOPTR_H
#include "../common/common.h"
namespace c_toto
{
template<class T>
class AutoPtr
{
public:
AutoPtr( AutoPtr<T> & ap ): ptr( ap.ptr )
{
ap.ptr = NULL;
}
AutoPtr<T> & operator=( AutoPtr<T> & ap )
{
if( ptr )
{
delete ptr;
ptr = NULL;
}
ptr = ap.ptr;
ap.ptr = NULL;
}
public:
AutoPtr( T * p = NULL ) : ptr( p ) {}
~AutoPtr()
{
delete ptr;
}
bool Valid()
{
if( ptr )
return true;
return false;
}
T & operator*() { return *ptr; }
const T & operator*() const { return *ptr; }
T * operator->() { return ptr; }
const T * operator->() const { return ptr; }
private:
T * ptr;
};
}; // namespace c_toto
#endif // #ifndef _PATTERN_AUTOPTR_H
需要注意的是,由于 AutoPtr 和指针是一对一的关系,那么 AutoPtr 类中的赋值操作符和拷贝构造函数必须保证只有一个 AutoPtr 指向对应的指针,在这里我们的策略是:
AutoPtr( AutoPtr<T> & ap ) 中的参数 AutoPtr ap 作废 ,构造的新 AutoPtr 接管原 AutoPtr 的指针。
AutoPtr<T> & operator=( AutoPtr<T> & ap ) 中的= 左值如果有指针 ,则 delete 掉原指针,接管右值的指针 ,右值作废。
这种简单的 AutoPtr 可以用于异常处理。当我们的函数执行中抛出异常,在异常前分配的资源需要在 catch 中手动释放,这样往往会有遗漏.
如果我们把分配的资源(往往是指针)存放在 AutoPtr 中,那么资源在超出它们的作用于时会自动释放,AutoPtr 会自动调用它们各自的析构函数。
引用计数句柄
这种句柄的目的是实现句柄和对象的多对一关系(对应于指针的情形就是多个指针指向同一对象),这样我们就可以按照常规定以来通过复制句柄来复制对象的指针。为了保证对象能够被释放,我们的句柄必须知道同时有多少个其他的句柄正指向当前的对象,所以我们引入引用计数策略。
(1) 这个引用计数功能不能放在句柄中. 因为如果句柄被复制,它的引用信息也会被复制,那么绑定了同一指针的句柄们的引用信息就无法统一管理。
(2) 把引用计数放在对象中也不合适,这需要我们改写现有类。我们可以建立一个中间类用来包含引用计数功能和需要用句柄绑定的指针。
可以看到,这个中间类和我们的指针是一对一关系 ,和我们的句柄是一对多关系。
现在然我们看看如何实现 Handel 中的基本操作:
(1) 默认构造函数
由于句柄现在面对的只是我们添加的中间类,所以只需简单的调用中间类的默认构造即可。在中间类的默认构造函数中我们将指针清零,引用置一。
(2) 拷贝构造函数
拷贝是对于句柄而言,我们通过将引用计数自加来避免对指针所值的内容拷贝。
(3) 赋值操作符
句柄间进行赋值操作时,=左边的句柄所指内容会被改写,所以需先让它的引用--(当引用为一时注意delete), 然后在++等号右边的句柄引用 。
用一个 handel 对 handel 自身的赋值是无意义的行为。
(4) 析构函数
每次析构时检查引用计数是否为一,如果是,说明当前句柄是最后一个保存这个指针的句柄,在析构函数中需要 delete 。
实际上我们可以将引用计数功能抽象成一个类,直接由句柄管理,这样就可以去掉中间层,减少程序复杂度。
/*
* File : sharedptr
* Discription : 加入了引用记数的指针存储策略
*/
#ifndef _PATTERNS_SHAREDPTR_H_
#define _PATTERNS_SHAREDPTR_H_
#include "../common/common.h"
namespace c_toto
{
template<class T>
class SharedPtr;
class Reference
{
public:
Reference() : ref_count( new int(1) ) { }
Reference( const Reference & r ) : ref_count( r.ref_count )
{
(*ref_count)++;
}
~Reference()
{
(*ref_count)--;
if( (*ref_count) == 0 )
delete ref_count;
}
bool Only()
{
return ( *ref_count == 1 );
}
bool Rebind( const Reference & r )
{
(*ref_count)--;
(*r.ref_count)++;
if( *ref_count == 0 )
{
delete ref_count;
ref_count = r.ref_count;
return true;
}
ref_count = r.ref_count;
return false;
}
private:
Reference & operator=( const Reference & r_ );
int * ref_count;
};
////////////////////////////////////////////////////////////////////////////////
template<class T>
class SharedPtr
{
public:
SharedPtr( T * p = NULL ): ptr( p ) {}
~SharedPtr() { if( ref.Only() ) { delete ptr; } }
SharedPtr<T> & operator=( const SharedPtr<T> & sp )
{
if( ref.Rebind( sp.ref ) )
{
delete ptr;
}
ptr = sp.ptr;
return *this;
}
private:
Reference ref;
T * ptr;
}; // class
}; // namespace c_toto
#endif // #ifndef _PATTERNS_SHAREDPTR_H_