沉思录,《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"< 那么我们就能够实现继承自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++沉思录》原书作者所有!
Tags: 

延伸阅读

最新评论

发表评论