作用:使设计的容器有能力包含类型不同而彼此相关的对象。
容器通常只能包含一种类型的对象,所以很难再容器中存储对象本身。存储指向对象的指针,虽然允许通过继承来处理类型不同的问题(多态性),但是也增加了内存分配的额外负担。所以我们通过定义名为代理的对象来解决该问题。代理运行起来和它所代表的对象基本相同,但是允许将整个派生层次压缩在一个对象类型中。
假设有一个表示不同种类的交通工具的类派生层次:
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++代理类设计(一)