C++之代理类笔记
代理类的引入
为什么需要代理,自然是客户端与服务器端不能正常沟通,需要第三方来进行沟通,这第三方自然成了代理,准确来说,应该是服务器端的代理,用来实现的代理功能的类就叫代理类。
那么什么时候客户端与服务器端不能正常沟通呢?《C++沉思录》有言:C++容器通常只能包含一种类型的对象。那么我们怎样才能设计一个C++容器,使它有能力包含类型不同而彼此相关的对象呢?首先想到的是容器里存储的是不是对象本身,而是指向对象的指针。这虽然看起来好像解决了问题,但存在两个缺点:一是存储的是指向对象的指针,不是对象本身,这样当对象被析构而指针没有被delete掉时,成了野指针。二是存储指针增加了内存分配的额外负担。基于此,我们通过定义名为代理(surrogate)的对象来解决上述问题。
问题
举例说明:
假设存在下面一个表示不同各类的交通工具的派生层次:
class Vehicle { public: virtual double weight()const =0; virtual void start() = 0; }; class RoadVehicle:public Vehicle {}; class AutoVehicle:public RoadVehicl {}; class Aircrate:public Vehicle {}; class Helicopter:public Aircraft {};
假如要跟踪处理一系列不同种类的Vehicle,定义如下:Vehicle parking_lot[1000]。这样没有达到预期效果,因为Vehicle是虚基类,不可能存在对象,更不可能存在其对象数组。
经典解决方案
既然上面不能存在对象本身,所以提供一个间接层,最易的就是存储指向对象的指针,即Vehicle *parking_lot[1000],但上面已说到这样有两个缺陷,其中最大的缺陷是指针容易成野指针。因为成为野指针的原因是指向的原对象被析构掉了,针对此可以变通下,放入数组parking_lot的不是指向原对象的指针,而是指向它们的副本的指针,如:
Helicopter x; parking_lot[num_vehicles++] = new Helicopter(x);
且采用一个约定:当释放parking_lot时,也释放其中所指向的全部对象。
存储的是指向对象副本的指针增加了动态内存管理的负担。
引入代理类解决方案
代理类与原类Vehicle的关系:1.行为和原类Vehicle相似;2.潜在地表示了所有继承自虚基类Vehicle的对象。
要潜在地表示了所有继承自虚基类Vehicle的对象,在C++中处理未知类型的对象的方法使用是使用虚函数。由于要复制任何类型的Vehicle,所以在Vehicle类中增加一个合适的虚函数:
class Vehicle { public: virtual double weight()const =0; virtual void start() = 0; virtual Vehicle* copy() const =0; virtual ~vehicle(){} };
比如Helicopter的copy函数为:
Vehicle* Helicopter::copy()const { return new Helicopter(*this); }
现在定义代理类:
class VehicleSurrogate
{
public:
VehicleSurrogate();
VehicleSurrogate(const Vehicle&);
VehicleSurrogate(const VehicleSurrogate&);
~VehicleSurrogate();
VehicleSurrogate& operator=(const VehicleSurrogate&);
double weight()const;
void start();
private:
Vehicle *vp;
};
代理类中定义构造函数VehicleSurrogate(const Vehicle&)来为任意继承自Vehicle的类的对象创建代理。代理类还有一个缺省构造函数,所以能够创建VehicleSurrogate对象的数组。然而缺省构造函数带来了问题,如何规定VehicleSurrogate的缺省操作?它所指向的对象的类型是什么?不可能是Vehicle,因为Vehicle是个抽象基类,根本没有Vehicle对象。所以引入定义行为
似于零指针的空代理(empty surrogate)的概念,能够创建、销毁和复制这样的代理,但是进行其他操作就视为出错。
下面是代理类成员函数的定义:
VehicleSurrogate::VehicleSurrogate():vp(0){} VehicleSurrogate::VehicleSurrogate(const Vehicle& v):vp(v.copy()){} 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::~VehicleSurrogate() { delete vp; } double VehicleSurrogate::weight()const { if(vp == 0) { throw "empty VehicleSurrogate.weight()"; } return vp->weight(); } void VehicleSurrogate::start() { if(vp == 0) { throw "empty VehicleSurrogate.start()"; } vp->start(); }
定义为了代理类,就可以定义Parking_lot[1000]了:
VehicleSurrogate parking_lot[1000]; Helicopter x; Parking_lot[num_vehicle++] = x;
最后一条语句等价为:
Parking_lot[num_vehicle++] = VehicleSurrogate(x);
小结
将继承和容器共用,迫使要处理两个问题:1.控制内存分配;2.把不同类型的对象放入同一个容器中。
代理类的每个对象都代表另一个对象,该对象可以是位于一个完整继承层次中的任何类的对象。通过在容器中用代理对象而不是对象本身。
Reference
1.《C++沉思录》第五章 代理类