C++代理类设计(一)

作用:使设计的容器有能力包含类型不同而彼此相关的对象。

容器通常只能包含一种类型的对象,所以很难再容器中存储对象本身。存储指向对象的指针,虽然允许通过继承来处理类型不同的问题(多态性),但是也增加了内存分配的额外负担。所以我们通过定义名为代理的对象来解决该问题。代理运行起来和它所代表的对象基本相同,但是允许将整个派生层次压缩在一个对象类型中。

假设有一个表示不同种类的交通工具的类派生层次:

class Vehicle
{
public:
 virtual double weight() const = 0;
 virtual void start() = 0;
 //...
};

class RoadVehicle:public Vehicle{/*...*/};
class AutoVehicle:public Vehicle{/*...*/};
class Aircraft:public Vehicle{/*...*/};
class Helicopter:public Vehicle{/*...*/};

可见Vehicle是一个抽象基类,有两个纯虚函数表示一些共有属性。下面请看下面这句话为什么不能达到预期的效果:

Vehicle parking_lot[1000];

表面上看是由于Vehicle是一个抽象基类,因此,只有从类Vehicle派生出来的类才能实例化,类Vehicle本身不会有对象,自然也就不会有对象数组了。

但是,假设我们剔除了类Vehicle中的所有纯虚函数,使其对象存在,那又会出现什么样的情况呢?看下面的语句:

Automobile x=/*...*/;

parking_lot[num_vehicles++] = x;

把x赋给parking_lot的元素,会把x转换成一个Vehicle对象,同时会丢失所有在Vehicle类中没有的成员。该赋值语句还会把这个被剪裁了的对象复制到parking_lot数组中去。这样,我们只能说parking_lot是Vehicle的集合,而不是所有继承自Vehicle的对象的集合。

经典解决方案------提供一个间接层

最早的合适的间接层形式就是存储指针,而不是对象本身:

Vehicle* parking_lot[1000];

然后,就有

Automobile x = /*...*/;

parking_lot[num_vehicles++] = &x;

这种方法解决了迫切的问题,但是也带来了两个新问题。

①我们存储在parking_lot中的是指向x的指针,在上例中是一个局部变量。这样,一旦变量x没有了,parking_lot就不知道指向什么东西了。

我们可以这么变通一下,放入parking_lot中的值,不是指向原对象的指针,而是指向它们的副本的指针。当我们释放parking_lot时,也释放其中所指向的全部对象。

②上述修改虽然不用存储指向本地对象的指针,但是它也带来了动态内存管理的负担。另外,只有当我们知道要放到parking_lot中的对象的静态类型后,这种方法才起作用。不知道又会怎样呢?看下面的:

if(p != q)

{

delete parking_lot[p];

parking_lot[p] = parking_lot[q];

}

这样的话,parking_lot[p]和parking_lot[q]将指向相同的对象,这不是我们想要的。在看下面的行不行:

if(p != q)

{

delete parking_lot[p];

parking_lot[p] = new Vehicle(*parking_lot[q]);

}

这样我们又回到了前面的问题:没有Vehicle类型的对象,即使有,也不是我们想要的(是经过剪裁后的对象)。

如何复制编译时类型未知的对象-------虚复制函数

我们在上面的Vehicle类中加入一个合适的纯虚函数:

class Vehicle

{

public:

virtual double weight() const = 0;

virtual void start() = 0;

virtual Vehicle* copy() const = 0;

//...

};

接下来,在每个派生自Vehicle的类中添加一个新的成员函数copy。指导思想就是,如果vp指向某个继承自Vehicle的不确定类的对象,那么vp->copy()会获得一个指针,该指针指向该对象的一个新建的副本。例如:如果Truck继承自(间接或直接)类Vehicle,则它的copy函数就类似于:

Vehicle* Truck::copy() const

{

return new Truck(*this);

}

当然,处理完一个对象后,需要清除该对象。要做到这一点,就必须确保类Vehicle有一个虚析构函数:

class Vehicle

{

public:

virtual double weight() const = 0;

virtual void start() = 0;

virtual Vehicle* copy() const = 0;

virtual ~Vehicle(){}

//...

};

有了上面的分析,下面我们就来定义代理类:

class VehicleSurrogate
{
public:
	VehicleSurrogate();
	VehicleSurrogate(const Vehicle&);
	~VehicleSurrogate();
	VehicleSurrogate(const VehicleSurrogate&);
	VehicleSurrogate& operator = (const VehicleSurrogate&);
private:
	Vehicle* vp;
};

上述代理类有一个以const Vehicle&为参数的构造函数,这样就能为任意继承自Vehicle的类的对象创建代理了(多态性,因为这里是引用参数)。同时,代理类还有一个缺省构造函数,所以我们能够创建VehicleSurrogate对象的数组。

然而,缺省构造函数也给我们带来了问题:如果Vehicle是个抽象基类,我们应该如何规定VehicleSurrogate的缺省操作呢?它所指向的对象的类型是什么呢?不可能是Vehicle,因为根本就没有Vehicle对象。为了得到一个更好的方法,我们要引入行为类似于零指针的空代理的概念。能够创建、销毁和复制这样的代理,但是进行其他的操作就视为出错。

下面看各个函数的定义:

VehicleSurrogate::VehicleSurrogate():vp(0){}

VehicleSurrogate::VehicleSurrogate(const Vehicle& v):vp(v.copy()){}//非零的检测室必要的,空代理

VehicleSurrogate::~VehicleSurrogate()

{

      delete vp;//C++标准里面对一个空指针运用delete也是没有问题的

}
VehicleSurrogate::VehicleSurrogate(const VehicleSurrogate& v):vp(v.vp?v.vp->copy():0){}

VehicleSurrogate& VehicleSurrogate::operator=(const VehicleSurrogate& v)

{

    if(this!=&v)//对赋值操作符进行检测,确保没有将代理赋值给它自身

    {

      delete vp;

      vp=(v.vp?v.vp->copy():0);//非零的检测是必要的,空代理

    }

    return *this;

}

下面就很容易定义我们的数组了:

VehicleSurrogate parking_lot[1000];

Automobile x;

parking_lot[num_vehicles++] = x;

最后一条语句就等价于

parking_lot[num_vehicles++] = VehicleSurrogate(x);

这个语句创建了一个关于对象x的副本,并将VehicleSurrogate对象绑定到该副本,然后将这个对象赋值给parking_lot的一个元素。当最后销毁parking_lot数组时,所有这些副本也将被清除。



C++代理类设计(一)

时间: 2024-10-31 08:30:18

C++代理类设计(一)的相关文章

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

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

使用NSProxy和NSObject设计代理类的差异

经常发现在一些需要使用消息转发而创建代理类时, 不同的程序员都有着不同的使用方法, 有些采用继承于NSObject, 而有一些采用继承自NSProxy. 二者都是Foundation框架中的基类, 并且都实现了<NSObject>这个接口, 从命名和文档中看NSProxy天生就是用来干这个事情的. 但即便如此, 它们却都定义了相同的消息转发的接口, 那我们在使用二者来完成这个工作时有什么差异呢. 先贴一下通过二者来创建代理类的最基本实现代码. 继承自NSProxy 12345678910111

iOS Class 使用NSProxy和NSObject设计代理类的差异

经常发现在一些需要使用消息转发而创建代理类时, 不同的程序员都有着不同的使用方法, 有些采用继承于NSObject, 而有一些采用继承自NSProxy. 二者都是Foundation框架中的基类, 并且都实现了<NSObject>这个接口, 从命名和文档中看NSProxy天生就是用来干这个事情的. 但即便如此, 它们却都定义了相同的消息转发的接口, 那我们在使用二者来完成这个工作时有什么差异呢. 先贴一下通过二者来创建代理类的最基本实现代码. 继承自NSProxy 12345678910111

c++ 沉思录---代理类

一.问题 如何设计一容器能包含彼此不同而又相互关联的类的对象(处于完整的继承层次的类)?因为一般的数组容器都只能包含一种类型的对象. 假设有一个表示不同类型的交通工具的类的派生层次: class Vehicle { public: virtual double weight () const = 0; virtual void start() = 0; //..... }; class RoadVehicle : public Vehicle { public: /* data */ }; cl

【C++沉思录】代理类

1.考虑下面的场景:设计一个容器,包含一组类型不同但相互关联的对象(比如:Animal,Dog,Cat),对象具备多态行为.2.容器一般只能包含一种类型的对象,使用vector<Animal> 会造成对象切割,不具备多态行为.3.经典的解决办法是:vector<Animal*>, 但是这会增加内存管理的负担.考虑下面的情况: Dog d; vec[i] = &d; // 局部对象d销毁, vec[i] 指向垃圾 vec[i] = vec[j]; // 指向同一个对象, 在v

c++中代理类的学习

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

在net.tcp模式下,由SvcUtil.exe生成代理类文件和配置文件(转)

WCF服务调用可以采用两个方法,由工具SvcUtil.exe生成本地代理服务类和配置文件方式,或者采用ChannelFactory直接创建服务代理对象.本文主要采用前面一种方式来进行. SvcUtil.exe位于:C:\Program Files\Microsoft SDKs\Windows\v7.0A\bin目录下,可以将本工具添加到VS2010的工具菜单中,以方便使用: VS菜单->工具->外部工具->添加->在“命令”文本框选取其路径如下:C:\Program Files\M

Spring AOP 实现原理(三) 使用 使用 CGLIB 生成代理类

CGLIB(Code Generation Library),简单来说,就是一个代码生成类库.它可以在运行时候动态是生成某个类的子类. 此处使用前面定义的 Chinese 类,现在改为直接使用 CGLIB 来生成代理,这个代理类同样可以实现 Spring AOP 代理所达到的效果. 下面先为 CGLIB 提供一个拦截器实现类: public class AroundAdvice implements MethodInterceptor { public Object intercept(Obje

利用wsdl.exe生成webservice代理类

根据提供的wsdl生成webservice代理类 1.开始->程序->Visual Studio 2005 命令提示 2.输入如下红色标记部分 D:/Program Files/Microsoft Visual Studio 8/VC>wsdl /language:c# /n:TestDemo /out:d:/Temp/TestService.cs D:/Temp/TestService.wsdl 在d:/Temp下就会产生一个TestService.cs 文件 注意:D:/Temp/T