回 帖 发 新 帖 刷新版面

主题:读C++c沉思录-第5章 代理

简单的说:代理(Surrogate)运行起来和它所代表的对象基本对象相同,但是允许将整个派生层次压缩在一个对象类型中.surrogate是handle类中最简单的一种.

  使用类的抽象,不仅仅是一种方法和技巧,不仅仅是解决问题时的一种下意识的行为,而应当成为一种思想,成为编程时时刻要遵循的一种思想,从而让这种技巧从实践的层面上升成为一种理论上的思考.

  好了,让我们来看一下一个伟大的代理类是如何从作者笔下诞生的吧!(作者不愧是个教授C++的高手)

  1,首先,是创建一个表示不同交通工具的类体系(类的派生层次),这个相信学过一个C++的人都能写出来.
    当然有一个基类,命名为Vehicle,这个Vehicle是个虚基类(你不会还有别的想法吧),它包含了一些交通工具的共同操作和属性.代码象下面这样:
    class Vehicle
    {
      virtual double weight() const = 0;  // 交通工具有自重
      virtual void start() = 0;           // 交通工具当然也能跑起来
      virtual void stop() = 0;            // 交通工具当然也能停下来
      ......          // 还有许多的操作,当然包括构造和析构
    }
    好了,基类有了,其他的交通工具就从这里派生吧.于是,有了:
    class RoadVehicle: public Vehicle{......}       // 直接派生Car或者Truck等具体类显然不是好主意
    class AutoVehicle: public RoadVehicle{......}   // 这样更有层次    
    class Aircraft: public Vehicle{......}          // 这个是空中的
    class Helicopter: public Aircraft{......}       // 终于到了具体的类
    class WaterVehicle: public Vehicle{......}    // 再来个水里的
    class Boat: public WaterVehicle{......}        // 小船
   .....            // 当然水路空都有了,具体的类可以有成千上万种.但基本都可以从上面的类来派生.

    类体系有了,现在我们要保存和处理这个类体系中的任意一种的交通工具.我们很自然会用基类数组去保存,你肯定不会为每个类建立一个数组.于是有了:
    Vehicle parking_lot[1000]; //先假设有一千个吧
但是这么做是有问题的,首先,Vehicle是个虚基类,它没有具体的对象,当然也就无法有对象的数组.而且当你把子类的对象赋值给基类的对象时,因为子类的对象通常比基类大,于是子类被裁减了.也就是说,子类被修剪成基类的大小.你不知道这会会给你程序带来什么.
    既然用对象不行,实际中经常使用的是指针.OK,是这样,Vehicle* parking_lot[1000];编译的确是没有问题了.但是用起来是比较麻烦的.
    如果这样用:
    AutoMobile x = ...;                 // 具体代码省略
      Parking_lot[num_Of_Vehicle++] = &x; // 这句没问题,问题是如果以后对象没有了,指针指到哪里去了?
    好,我们再想办法,改成这样:
    Parking_lot[num_Of_Vehicle++] = new Automobile(x);  // 这个办法还行,但是以后这个数组归你自己管了,自己负责释放所有的指针,不能漏掉.
    尤其还有一个麻烦,如果要让数组中的一个元素,如Parking_lot[p],等于数组中的另一个元素Parking_lot[q],怎么办呢?仿佛很容易,很多人提起笔就能写:
    delete Parking_lot[p]; // 先删除原指针
     Parking_lot[p] = Parking_lot[q]; // 赋值简单吧,有问题吗?
有~!!!
Parking_lot[p] 和 Parking_lot[q]指向了相同的对象,删除一个指针Parking_lot[p]的时候,另一个Parking_lot[q]指向了哪里呢?如果你还使用那个指针Parking_lot[q]的话,结果将无法预料.
有人还想改良一下,改成这样:
   delete Parking_lot[p]; // 先删除原指针
   Parking_lot[p] = new Vehicle(Parking_lot[q]); // 新创建一个指针,总可以吧?
不可以,别忘了Vehicle是虚基类.我们也不知道Parking_lot中的指针是指向的是什么对象.如果你要自己搞一套进行运行时类型识别的东西,那我就没辙了.(你的主意很酷,但是不是用在这里的)
  好,怎么办?只要想一个办法,复制这个对象就可以了.在对象外面new不行,我们就在对象里面实现.不过既然要支持所有的Vehicle,那么最好就是在虚基类里声明这个接口了.于是就添加一个纯虚函数:
    virtual Vehicle* copy() const = 0;

    好的,在每个派生类里实现它吧.具体代码象这样:
    Vehicle* Truck::copy() const
    {
       return new Truck(*this);
    }

  好了,问题解决了.现在我们提出一个更高的要求,能不能避免显式的处理内存分配,同时又保持运行时绑定的属性呢?
  作者说,用类来表示概念.这是C++设计的基本原则.在复制对象的过程中运用这个设计原则,就是定义一个行为和Vehicle对象类似,而又潜在的表示所有继承自Vehicle类的对象的东西.我们称这种类的对象叫做代理.
  这个代理有哪些性质呢?首先,每个代理类都代表某个继承自基类的对象.只要代理还关联着这个对象,该对象就一定存在.对代理的操作也就相应的对这个对象进行操作.
  好了,我们知道了代理该做些什么事情,那么这个代理类就可以这么定义:
  class VehicleSurrogate
  {
  public:
     VehicleSurrogate();   // 缺省构造函数,这样就可以构建数组
     VehicleSurrogate(const Vehicle&); // 从Vehicle对象构造一个代理,没有问题
     ~VehicleSurrogate();
     VehicleSurrogate(const VehicleSurrogate&);  // 拷贝构造函数,容易构建一个新对象
     VehicleSurrogate& operator=(const VehicleSurrogate&);  // 赋值操作符
  private:
     Vehicle* vp;        // 真正的代理的对象
  }

  缺省构造函数会带来一个问题,就是如何规定VehicleSurrogate的缺省操作.缺省的情况下它所指向的对象类型是什么?不能是Vehicle对象.
  于是引入类似于零指针的空代理(empty surrogate),空代理可以被创建,销毁和复制,不能进行其他操作.
  根据这些要求,来写出成员函数的定义:
  VehicleSurrogate::VehicleSurrogate():vp(0) {} // 缺省构造
  VehicleSurrogate::VehicleSurrogate(const Vehicle& v) 
  : vp(v.copy()) { }  // 是copy,不是v,因为参数是引用,所以copy函数是动态绑定的
  VehicleSurrogate::VehicleSurrogate(const VehicleSurrogate& v)
  :vp(v.vp?v.vp->copy():0) { }
  // 这里进行了参数指针的非零检测,防止copy的调用失败.
  VehicleSurrogate::~VehicleSurrogate()
  {
    delete vp;
  }
  VehicleSurrogaet& VehicleSurrogate::operator=(const VehicleSurrogate& v) 
  {
      if(this != &v)    // 防止赋值给自己
    {
      delete vp;
      vp = (v.vp? v.vp->copy() : 0);
    }
    return *this;
  }
  然后,还要支持被代理的类的其他操作,如:
  double weight() const;
  void start();
  void stop(); 
  ......
       
  他们的定义也很简单,主要是将操作传递到具体的Vehicle对象,代码象下面这样:
  double VehicleSurrogate::weight() const 
  {
    if(vp == 0)
    {
    throw "empty VehicleSurrogate.weight()";    // 这里抛出异常了
    }
    return vp->weight();        // 把操作传递给具体的Vehicle指针
  }


  到现在,我们终于拥有了神奇的代理类.

  我们有了代理类,那么就要重新定义一下我们的Parking_lot[]了,改成这样:
              VehicleSurrogate Parking_lot[1000];
      Automobile x;
     Parking_lot[num_of_Vehicle++] = x;

  OK,代码里面没有指针,你可以象使用基本数据类型一样使用他们.
    

回复列表 (共2个回复)

沙发


感觉这段代码就像教材似的,太经典了

板凳

支持

我来回复

您尚未登录,请登录后再回复。点此登录或注册