问题
怎样在容器中包含类型不同而彼此相关的对象呢?
可以在容器存储指针,通过继承来处理不同的问题,但是增加了内存分配的额外负担。
通过代理类可以解决该问题,代理运行起来和所代表的对象基本相同。但是允许将整个派生层次压缩在一个对象类型中,完成一个容器包含类型不同对象的目的。
class Vehicle {
public:
virtual double weight() = 0 ;
virtual void start() = 0 ;
};
class RoadVehicle : public Vehicle {...}
class AutoVehicle : public RoadVehicle {...};
使用某种容器(例如数组)来处理不同种类的Vehicle。
Vehicle parking_lot[1000];
AutoVehicle x;
parking_lot[num_vehicles++] = x;
上述代码有两个问题:
- Vehicle是虚基类(抽象基类),本身不会有对象,只有派生出来的类才能够实例化。对象不存在,对象数组也就不存在。
- 即使存在对象,x转换成Vehicle也会出现对象被裁剪,丢失所有Vehicle类中没有的成员。这样容器是Vehicle对象的集合,而不是所有继承自Vehicle的对象的集合。
解决方案一
提供一个间接层,即容器中存储对象指针。
Vehicle* parking_lot[1000]; // 指针数组
AutoVehicle x;
parking_lot[num_vehicles++] = &x;
解决了迫切的问题,但带来了两个问题:
- x是临时变量,一旦销毁,parking_lot就不知道指向什么东西了;
Vehicle* parking_lot[1000]; // 指针数组
AutoVehicle x;
parking_lot[num_vehicles++] = new AutoVehicle(x);
- 虽然不用存储局部对象指针,但带来了动态内存管理的负担。而且只有知道放到parking_lot的静态类型之后才有作用。如果不知道类型,就只能指向新建的Vehicle。
如果parkingcl_lot[p]和parkingcl_lot[q]指向的对象相同,下面这些语句就会出错。
if (p != q) {
delete parking_lot[p];
parking_lot[p] = parking_lot[q];
}
// 或
if (p != q) {
delete parking_lot[p];
parking_lot[p] = new Vehicle(parking_lot(q);
// 回到以前的问题,Vehicle对象不存在,即使存在也不是想要的
}
解决方案二
- 先增加个虚复制函数copy, 复制编译时类型未知的对象:
class Vehicle {
public:
virtual double weight() = 0 ;
virtual void start() = 0 ;
virtual Vehicle* copy() = 0;
virtual ~Vehicle() {}; // 清除对象
};
class RoadVehicle : public Vehicle {
public:
// ...
virtual Vehicle* copy()
{
return new RoadVehicle(*this);
}
};
- 用类表示概念,定义一个代理类。
定义一个和Vehicle对象相似,又潜在地表示了所有继承自Vehicle类的对象的东西,这种类的对象叫做代理。
class VehicleSurrogate {
public:
// 默认构造能够创建VehicleSurrogate数组
VehicleSurrogate() : vp(nullptr) {}
// 为任意继承Vehicle类的对象创建代理
VehicleSurrogate(const Vehicle& v) : vp(v.copy()) {}
~VehicleSurrogate() { delete vp; }
VehicleSurrogate(const VehicleSurrogate& v) : vp(v.vp ? v.vp->copy() : nullptr) {}
VehicleSurrogate& operator=(const VehicleSurrogate& v)
{
if (this != &v) {
delete vp;
vp = v.vp ? v.vp->copy() : nullptr;
}
return *this;
}
virtual double weight()
{
if (vp == nullptr) {
throw "empty vehiclesurrogate weight";
}
return vp.weight();
}
virtual void start()
{
if (vp == nullptr) {
throw "empty vehiclesurrogate start";
}
return vp.start();
}
private:
Vehicle *vp;
};
默认构造带来了的问题是:如果Vehicle是抽象基类,如何处理VehicleSurrogate默认操作,vp指向的不可能是Vehicle对象。vp只能是空指针,即空代理的概念。这种默认操作只允许创建,销毁和复制,不能进行其他操作。
VehicleSurrogate parking_lot[1000]; // 代理对象数组
AutoVehicle x;
parking_lot[num_vehicles++] = x;
// 等价于 parking_lot[num_vehicles++] = VehicleSurrogate(x)
// 创建x的副本,并把VehicleSurrogate对象绑定到该副本,然后赋值给数组元素
// 最后数组销毁,这些副本也跟着销毁。
总结
解决两个问题:控制内存分配和存储不同类型对象的容器。
采用c++基础技术(继承,多态),用类表示概念,提出代理类,每个对象都代表另一个对象,该对象可以是位于一个完整继承层次中的任何类的对象。通过在容器中使用代理类的对象而不是对象本身的方式,解决了我们的问题,其实也是采用了间接层思想(万能钥匙)。