C++中代理类和句柄类

指针是 C 与其他语言区别的重要特征之一,在 C++ 中,指针也被广泛运用,我们通过指针实现多态。然而,众所周知,指针的使用必须小心,否则很容易造成内存泄漏 Memory Leak。当我们有几个指针指向同一个对象时有其应该注意,关于何时释放这个对象:
(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_

http://www.cnblogs.com/yc_sunniwell/archive/2010/07/12/1775619.html

时间: 2024-08-03 18:42:01

C++中代理类和句柄类的相关文章

《C++沉思录》——类设计核查表、代理类、句柄类

<C++沉思录>集中反映C++的关键思想和编程技术,讲述如何编程,讲述为什么要这么编程,讲述程序设计的原则和方法,讲述如何思考C++编程. 一.类设计核查表 1.你的类需要一个构造函数吗? 2.你的数据成员都是私有的合理吗? 3.你的类需要一个无参的构造函数吗? 是否需要生成类对象的数组! 4.你的每一个构造函数都初始化所有的数据成员了吗? 虽然这种说法未必总是正确,但是要积极思考! 5.你的类需要析构函数吗? 6.你的类需要一个虚析构函数吗? 7.你的类需要一个拷贝构造函数吗? 8.你的类需

句柄类与继承

前一小节<容器与继承>http://blog.csdn.net/thefutureisour/article/details/7744790提到过: 对于容器,假设定义为基类类型,那么则不能通过容器訪问派生类新增的成员:假设定义为派生类类型,一般不能用它承载基类的对象,即使利用类型转化强行承载,则基类对象能够訪问没有意义的派生类成员,这样做是非常危急的.对这个问题的解决的方法,是使用容器保存基类的指针. 在C++中,这类问题有一种通用的解决的方法,称为句柄类.它大体上完毕双方面的工作: 1.管

智能指针与句柄类(四)

当我们希望使用容器来保存继承体系中的对象时,容器用于继承体系中的类型会有影响:派生类对象复制到基类对象时,派生类对象将被切掉.那么解决这一问题的方法通常是使用容器保存基类对象的指针,这些指针实际指向的是程序运行时动态分配的派生类对象,用户必须保证在容器析构前调用delete来释放动态分配的对象,如下例: 1 class Base 2 { 3 public: 4 virtual void action() = 0; 5 }; 6 class Derived1 : public Base 7 {..

0722-----C++Primer听课笔记----------句柄类和智能指针

1.再说智能指针 1.1  为什么要用智能指针?对于一个指针,它指向一个动态分配内存的对象,若同时有多个指针指向该对象,那么当我们delete的时候,就会出现delete 一个无效内存的错误,因为该对象已经被delete过了,所以这就造成了错误.针对这一情况,我们想到,new 和 delete 必须是成对出现的,那么联想到类里面,很容易想到这个构造函数和析构函数也是成对出现的,对于每一个对象,初始化的时候会调用构造函数,而销毁的时候必然要调用析构函数.因此我们就可以对 指针 进行封装,将该指针的

C++ Primer 学习笔记_71_面向对象编程 --句柄类与继承

面向对象编程 --句柄类与继承 引言: C++中面向对象编程的一个颇具讽刺意味的地方是:不能使用对象支持面向对象编程,相反,必须使用指针或引用. void get_prices(Item_base object, Item_base *pointer, Item_base &reference){ //需要根据指针或引用实际所绑定的类型进行调用 cout<< pointer ->net_price(1)<< endl; cout<< reference.n

Java中动态代理技术生成的类与原始类的区别 (转)

用动态代理的时候,对它新生成的类长什么样子感到好奇.有幸通过一些资料消除了心里的疑惑. 平时工作使用的Spring框架里面有一个AOP(面向切面)的机制,只知道它是把类重新生成了一遍,在切面上加上了后来定义的逻辑.这样就达到了动态的在原有类上增加一些功能.比如日志打印,拦截信息等. 这里只关心动态代理技术生成新的类,先不管虚拟机是如何去生成类,用了什么字节码生成技术,怎么产生字节码等这一系列动作.现在只关心最后生成的新类长什么样,它和老类有什么区别.为了获取到生成后的代理类的字节码并且反编译成我

c++中代理类的学习

https://blog.csdn.net/lcg910978041/article/details/51468680 C++代理类是为了解决这样的问题: 容器通常只能包含一种类型的对象,所以很难在容器中存储对象本身. 怎样设计一个c++容器,使它有能力包含类型不同而彼此相关的对象?  代理运行起来和他所代表的对象基本相同,但是允许将整个派生层次压缩在一个对象类型中. 代理类的每个对象都代表另一个对象,该对象可以是位于一个完整继承层次中的任何类的对象.通过在容器中使用代理对象而不是对象本身的方式

ZeroC ICE源代码中的那些事 - 嵌套类和局部类

使用嵌套类(类中定义的类,c++没有静态类)或局部类(在函数或成员方法中定义的类),进行行为模式的委托(委托请求)或异步 . java中嵌套类和局部类隐式完成了你对外部对象(实例)访问的私有堆栈的初始化,而c++你必须通过类成员变量来保存你要访问到的 外部对象(变量,实例),即自行实现私有堆栈,并使用嵌套类和局部类的构造函数,以参数传递的方式初始化这些用作私有堆栈的成 员变量.当你的局部类(在函数中定义的类)要访问到函数的局部变量时,你并不能确保局部类在局部变量被释放前访问,所以你只能 将局部变

深度模拟java动态代理实现机制系类之三

这里的内容就比较复杂了,要实现的是对任意的接口,对任意指定的方法,以及对任意指定的代理类型进行代理,就更真实的模拟出java虚拟机的动态代理机制 罗列一下这里涉及的类.接口之间的关系,方便大家学习.1.InvocationHandler接口,用来处理指定的方法,即对特定方法的代理,处理的具体实现交由子类实现2.TimeHandler类,实现了InvocationHandler接口的子类,具体的代理实现是进行时间代理3.Proxy类,用于产生代理类的类4.Moveable接口,举例过程中各类要实现