《C++沉思录》摘引——代理类(Surrogate)
问题引入
假设有一个表示不同种类的交通工具的类派生层次:
2 {
3 public:
4 virtual void start() = 0;
5 // ...
6 };
7 class RoadVehicle : public Vehicle {};
8 class AutoVehicle : public RoadVehicle {};
9 class Aircraft : public Vehicle {};
10 class Helicopter : public Aircraft {};
其中,Vehicle是一个抽象基类。在实际中,我们可能会使用某种容器类,比如数组:
上述定义没有产生预期的效果,为什么?由于Vehicle本身不会有对象,也就不可能有其对象数组了。
如果我们去掉类Vehicle的所有纯虚函数,写出类似于下面的语句,会有什么效果呢?
2 parking_lot[i] = x;
答案是:把x赋给parking_lot的元素,会把x转换成一个Vehicle对象,同时会丢失所有在Vehicle类中没有的成员。该赋值语句还会把这个剪裁了的对象复制到parking_lot数组中。
这样,我们只能说parking_lot是Vehicles的集合,而不是所有继承自Vehicle的对象的集合。
使用代理类
有没有一种方法既能让我们避免显示地处理内存分配,又能保持类Vehicle运行时绑定的属性呢?
有!方法是:定义一个行为和Vehicle对象类似,而又潜在地表示了所有继承自Vehicle类的对象的东西。我们把这种类的对象叫做代理(surrogate)。
2 {
3 public:
4 VehicleSurrogate() : vp(NULL) {};
5 VehicleSurrogate(const Vehicle& v) : vp(v.copy()) {};
6 ~VehicleSurrogate() {};
7 VehicleSurrogate(const VehicleSurrogate& v) : vp(v.vp ? v.vp->copy() : NULL) {}; //v.vp非零检测
8 VehicleSurrogate& operator=(const VehicleSurrogate& v)
9 {
10 if (this != &v) // 确保没有将代理赋值给它自身
11 {
12 delete vp;
13 vp = (v.vp ? v.vp->copy() : NULL);
14 }
15
16 return *this;
17 };
18
19 //来自类Vehicle的操作
20 void start()
21 {
22 if (vp == 0)
23 throw "empty VehicleSurrogate.start()";
24
25 vp->start();
26 };
27
28 private:
29 Vehicle* vp;
30 };
我们再定义两个Vehicle的子类Car和Truck:
2 {
3 public:
4 virtual void start() = 0;
5 virtual Vehicle* copy() const = 0; //虚拷贝
6 virtual ~Vehicle() {}
7 };
8
9 class RoadVehicle : public Vehicle
10 {
11 public:
12 void start() { cout<<"start road vehicle"<<endl; } ;
13 Vehicle* copy() const { return new RoadVehicle(*this); };
14 ~RoadVehicle() {};
15 };
16
17 class Truck : public RoadVehicle
18 {
19 public:
20 void start() { cout<<"start truck"<<endl; } ;
21 Vehicle* copy() const { return new Truck(*this); };
22 ~Truck() {};
23 };
24
25 class Car : public RoadVehicle
26 {
27 public:
28 void start() { cout<<"start car"<<endl; } ;
29 Vehicle* copy() const { return new Car(*this); };
30 ~Car() {};
31 };
那么我们就能够实现继承自Vehicle的类的对象的容器:
2 {
3 VehicleSurrogate parking_lot[10];
4 Truck x;
5 Car y;
6 for (int i=0; i<10; i++)
7 {
8 // 相当于parking_lot[i] = VehicleSurrogate(x);
9 // 创建了一个关于对象x的副本,并将VehicleSurrogate对象绑定到该副本,
10 // 然后将这个对象赋值给parking_lot的一个元素。当最后销毁parking_lot数组时,
11 // 所有这些副本也将被消除
12 if (i&1)
13 parking_lot[i] = x; //VehicleSurrogate(x);
14 else
15 parking_lot[i] = y; //VehicleSurrogate(y);
16
17 parking_lot[i].start();
18 }
19
20 return 0;
21 }
结果为:
start car
start truck
start car
start truck
start car
start truck
start car
start truck
start car
start truck
每个Vehicle代理都代表某个继承自Vehicle类的对象。只要该代理关联着这个对象,该对象就肯定存在。
因此,拷贝代理就会拷贝相应的对象,而给代理赋新值也会先删除旧对象,再拷贝新对象(改变代理类实际关联的那个对象的类型)。
所幸的是,我们在类Vehicle中已有虚拷贝函数copy来完成这些拷贝工作。
总结
将继承和容器共用,迫使我们要处理两个问题:控制内存分配和把不同类型的对象放入同一容器中。采用基础的C++技术,用类来表示概念,我们可以同时兼顾这两个问题。我们提出一个名叫代理类的类,这个类的每一个对象都代表另一个对象,该对象可以是位于一个完整继承层次中的任何类的对象。通过在容器中用代理对象而不是对象本身的方式,解决了我们的问题。
声明
本文版权归《C++沉思录》原书作者所有!