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++代理类设计(一),布布扣,bubuko.com

C++代理类设计(一)

上一篇:为什么说Python是一门动态语言--Python的魅力


下一篇:python实现阶乘阶乘--reduce函数