你带酒来,我有故事

C++之代理类

:: 代码生涯 二十画生 1214℃ 0评论

代理类的引入

  为什么需要代理,自然是客户端与服务器端不能正常沟通,需要第三方来进行沟通,这第三方自然成了代理,准确来说,应该是服务器端的代理,用来实现的代理功能的类就叫代理类。

那么什么时候客户端与服务器端不能正常沟通呢?《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++沉思录》第五章 代理类

转载请注明:二十画生 » C++之代理类

喜欢 (0)
发表我的评论
取消评论

表情

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址