c++沉思录--代理类

  正如每个c++程序员都应该知道的那样,只有在程序通过指向基类对象的指针或者基类对象的引用调用虚函数时,才会发生运行时的多态现象。

  这个模型的含义可能不太明显。特别是对象的创建和复制不是运行时多态的,这一点严重地影响了类的设计。所以,容器(无论是类似于数组或者结构体的内建容器还是用户自定义容器类)只能获得编译时类型已知的元素值。如果有一系列类之间存在继承关系,当我们需要创建、复制和存储对象,而这些对象的确切类型只有到运行时才能知道时,则这种编译时的检查会带来一些麻烦。

  通常,解决这个问题的方法是增加一个间接层。传统的C模型可能会建议采用指针来实现这个间接层。这样做会给需要使用这些类的用户带来负面影响,使得他们必须参与内存管理,这可是一个单调冗长而且极易犯错的工作。c++采用了一种更自然的方法,就是定义一个类来提供并且隐藏这个间接层。

  这种类通常叫做句柄类。句柄类采用最简单的形式,把一个单一类型的对象与一个与之有某种特定继承关系的任意类型的对象捆绑起来。所以,句柄可以让我们忽略正在处理的对象的准确类型,同时还能避免指针带来的关于内存管理的麻烦。句柄类的一个常见用途就是通过避免不必要的复制来优化内存管理。

  首先介绍代理(surrogate)是handle类中最简单的一种。

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

  

class Vehicle {
    public:
            virtual double weight() const =0;
            virtual void start () =0;

            //...

};

class RoadVehicle :public Vehicle{...};
class AutoVehicle: public RoadVehicle {...};
class Aircraft :public Vehicle {...};
class Helicopter: public Aircraft {...};

  所有Vehicle都有一些类Vehicle中的成员声明的共有属性。但是,有的Vehicle具有一些其他Vehicle所没有的属性。

  下面假设我们要跟踪处理一系列不同种类的Vehicle。在实际中,我们可能会使用某种容器类;

  首先我们想一个办法来复制编译时类型未知的对象。我们知道,C++中处理未知类型的对象的方法就是使用虚函数。

  由于我们是想能够复制任何类型的Vehicle,所有应该在Vehicle类中增加一个合适的纯虚函数:

  

class Vehicle{
    
    public:
            virtual double weight() const = 0;
            virtual void start() = 0;
            virtual Vehicle* copy() const = 0;
            virtual ~Vehicle() {}; 
            //...

};

//接下来,在每个派生自Vehicle的类中添加一个新的成员函数copy。如果vp指向某个继承自Vehicle的不确定类的对象,则vp->copy()会获得一个指针,该指针指向该对象的一个新建的副本。如Truck继承Vehicle,

Vehicle * Truck::copy() const
{
     return new Truck(*this);    
}

  为了避免显示地处理内存分配,又能保持类Vehicle在运行时绑定的属性。

  我们开始定义代理类,每个Vehicle代理都代表某个继承自Vehicle类的对象。只要该代理关联着这个对象,该对象就肯定存在。

class VehicleSurrogate {

    public:
            VehicleSurrogate();
            VehicleSurrogate(const Vehicle &);
            VehicleSurrogate(const VehicleSurrogate &);
            VehicleSurrogate &operator=(const VehicleSurrogate &);
            ~VehicleSurrogate();
            //来自类Vehicle的操作
       double weight() const;
       void start();
....
private: Vehicle *vp; }; /* 上述代理类有一个以const Vehicle &为参数的构造函数,这样就能为任意继承自Vehicle的类的对象创建代理了。同时,代理类还有一个缺省构造函数,所以我们能够创建VehicleSurrogate对象的数组。然而,缺省构造函数也给我们带了问题:如果Vehicle是个抽象基类,我们应该如何规定呢?我们引入行为类似于零指针的空代理,能够创建、销毁和复制这样的代理,但是进行其他操作就出错。 */ VehicleSurrogate::VehicleSurrogate() :vp(0) {} VehicleSurrogate::VehicleSurrogate(const Vehicle &v) :vp(v.copy()) {} VehicleSurrogate::VehicleSurrogate(const VehicleSurrogate &vs) :vp(vs.vp? vs.vp->copy() : 0) {} VehicleSurrogate:: VehicleSurrogate &operator=(const VehicleSurrogate &vs) { if(&vs != this) { delete vp; vp = (vs.vp ? v.vp->copy() : 0); } return *this; }

  注意这些函数都不是虚拟的:我们这里所使用的对象都是类VehicleSurrogate的对象;没有继承自该类的对象。当然,函数本身可以调用相应Vehicle对象中的虚函数。它们也应该检查确保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();
}

  一旦完成以上工作,我们就能容易定义我们数组了

  VehicleSurrogate parking_lot[1000];

  Automobile x;

  parking_lot[num_vehicles++] = x;

  最后一条语句等价于

  parking_lot[num_vehicles++] = VehicleSurrogate(x);

  这个语句创建了一个关于对象x的副本,并将VehicleSurrogate对象绑定到该副本,然后将这个对象赋值给parking_lot的一个元素。当最后销毁parking_lot数组时,所有这些副本也将被清除。

  小结:将继承和容器共用,迫使我们要处理两个问题:控制内存分配和把不同类型的对象放入同一个容器中。采用基础的C++技术,用类来表示概念,我们可以同时兼顾这两个问题。做这些事情的时候,我们提出一个名叫代理类的类,这个类的每个对象都代表另一个对象,该对象可以是位于一个完整继承层次中的任何类的对象。通过在容器中用代理对象而不是对象本身的方式,解决了我们的问题。

 

  

    

  

  

posted @ 2015-04-09 15:56  bigshowxin  阅读(150)  评论(0编辑  收藏  举报