《C++沉思录》摘引——代理类(Surrogate)

问题引入

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

 1 class Vehicle {}
 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是一个抽象基类。在实际中,我们可能会使用某种容器类,比如数组:

1 Vehicle parking_lot[10];

 上述定义没有产生预期的效果,为什么?由于Vehicle本身不会有对象,也就不可能有其对象数组了。

 

如果我们去掉类Vehicle的所有纯虚函数,写出类似于下面的语句,会有什么效果呢?

1 Automobile x = /* ... */;
2 parking_lot[i] = x;

 答案是:把x赋给parking_lot的元素,会把x转换成一个Vehicle对象,同时会丢失所有在Vehicle类中没有的成员。该赋值语句还会把这个剪裁了的对象复制到parking_lot数组中。

这样,我们只能说parking_lot是Vehicles的集合,而不是所有继承自Vehicle的对象的集合。

 

使用代理类

 有没有一种方法既能让我们避免显示地处理内存分配,又能保持类Vehicle运行时绑定的属性呢?

有!方法是:定义一个行为和Vehicle对象类似,而又潜在地表示了所有继承自Vehicle类的对象的东西。我们把这种类的对象叫做代理(surrogate)。

 1 class VehicleSurrogate
 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:

 1 class Vehicle
 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的类的对象的容器:

 1 int _tmain(int argc, _TCHAR* argv[])
 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++沉思录》原书作者所有!

 

posted on 2011-06-23 14:19  burellow  阅读(868)  评论(0编辑  收藏  举报

导航