类和继承(二):代理类

如何设计C++容器,能够包含类型不同而彼此相关的对象
如何将继承自同一父类的属于不同子类的对象装入同一个容器(如vector)之中?
(将容器和继承运用在一起)

  • 代理 (surrogate) 允许将整个派生层次压缩在一个对象类型中

surrogate是handle(句柄)类中最简单的一种

一个表示不同交通工具的类派生层次

1
2
3
4
5
6
7
8
9
10
11
class Vehicle {
public:
virtual double weight() const = 0;
virtual void start() = 0;
// pure virtual function
// ...
}
class RoadVehicle: public Vehicle {/*...*/}
class AutoVehicle: public Vehicle {/*...*/}
class Aircraft : public Vehicle {/*...*/}
class Helicopter : public Vehicle {/*...*/}

一个容器

1
2
3
Vehicle parking_lot[1000]; // error
AutoVehicle x;
parking_lot[num_vehicles++] = x;
  • Vehicle是虚基类,不能实例化
  • 子类对象转化为父类对象会丢失父类中没有的成员
  • parking_lot是Vehicle的集合而不是所有继承自Vehicle的对象的集合

间接层 indirection

  • 存储指针替代存储对象本身:
1
2
3
Vehicle* parking_lot[1000];
AutoVehicle x;
parking_lot[num_vehicles++] = &x;
  • x是局部变量,释放之后parking_pot指向未知

  • 存储副本的指针而非原对象的指针:

1
2
AutoVehicle x;
parking_lot[num_vehicles++] = new AutoVehicle(x);
  • 带来动态内存管理的负担
  • 必须知道要放入parking_lot中的对象的静态类型
1
2
3
4
if (p != q) {
delete parking_pot[p];
parking_pot[p] = new Vehicle(parking_pot[q]);
} // virtual base class has no instance

虚复制函数

  • 在Vehicle中增加合适的纯虚函数来复制编译时类型未知的对象:
1
2
3
4
5
6
7
8
class Vehicle {
public:
virtual double weight() const = 0;
virtual void start() = 0;
virtual Vehicle* copy() const = 0;
virtual ~Vehicle() {} // virtual destructor
// ...
}
  • 在Vehicle的所有派生类中添加成员函数copy,若vp指向Vehicle不确定的子类的对象,vp->copy()返回指向该对象新建副本的指针

  • 例如:

1
2
3
Vehicle* Trunk::copy() const {
return new Truck(*this);
}

定义代理类

  • 用类表示概念
    • 避免显示处理内存分配且能保持父类在运行时绑定的属性
  • 定义一个行为与Vehicle对象相似而又潜在地表示所有继承自Vehicle对象的东西

    • 代理 (surrogate)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class VehicleSurrogate {
public:
VehicleSurrogate();
VehicleSurrogate(const Vehicle&);
~VehicleSurrogate();
VehicleSurrogate(const VehicleSurrogate&);
VehicleSurrogate& operator=(const VehicleSurrogate&);
// object fuctions of Vehicle
double weight() const;
void start();
// ...
private:
Vehicle* vp;
}
  • 空代理(empty surrogate)的行为类似于空指针
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
VehicleSurrogate::VehicleSurrogate(): vp(0) {} // empty surrogate
VehicleSurrogate::VehicleSurrogate(const Vehicle& v): vp(v.copy()) {}
VehicleSurrogate::~VehicleSurrogate() {
delete vp;
}
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;
}
// call the corresponding object functions
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();
}

每次对copy的调用都是一个虚拟调用。类Vehicle的对象并不存在
赋值构造函数和赋值操作符中v.vp非零的检测是必需的
赋值操作符确保没有将代理赋值给自身

总结

  • 最开始的parking_pot容器可设计为:
1
2
3
VehicleSurrogate parking_lot[1000];
Automobile x;
parking_pot[num_vehicles++] = x;
  • 将继承和容器共用需要处理两个问题:

    • 控制内存分配
    • 把不同类型的对象放进同一个容器中

采用基础C++技术,在现有的继承层次上加上一层抽象,合适地解决了这些问题


《C++沉思录(Cplusplus Thinking)》笔记