代理类
引言
怎么设计一个c++容器,使它有能力包含类型不同而彼此相关的对象呢?
容器通常只能包含一种类型的对象,所以很难在容器中存储对象本身。
存储指向对象的指针,虽然允许通过继承来处理类型不同的问题,但是也增加了内存分配的额外负担。
这里讨论一种方法,通过定义名为 代理(surrogate) 的对象来解决该问题。代理运行起来和它所代表的对象基本相同,
但是允许将整个派生层次压缩在一个对象类型中。
5.1 问题
假设有不同种类的交通工具的类派生层次,不同的派生类都有一些基类的共有属性,但也有一些自己独有的属性(飞翔、盘旋等)。
// 基类:Vehicle
// 派生类:RoadVehicle AutoVehicle Aircraft ...
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 Hilicopter : public Aircraft { /*...*/ }; // 直升飞机
下面假设我们要处理一系列不同种类的Vehicle。
在实际开发中我们可能会使用某种容器类vector<Vehicle> parking_lot;
但是为了表述更简洁,使用数组来实现Vehicle parking_lot[1000]; //parking lot 停车场
;
但有一个问题,由于 Vehicle 是纯虚函数,所以不可能实例化出来对象,当然不可能存在 Vehicle 对象数组了!
5.2 经典解决方案
常见做法:提供一个间接层(indirection)。
最早的合适的间接层形式就是存储指针,而不是对象本身:Vehicle* parking_lot[1000]; //指针数组
然后输入以下代码:
Vehicle* parking_lot[1000];
int num_vehicles = 0;
Automobile auto_mobile = /*...*/;
Parking_lot[num_vehicles++] = &auto_mobile;
带来了两个新问题:
问题1: 首先 auto_mobile 是局部变量,一旦 auto_mobile 没有了,parking_lot 就不知道指向哪里了。
变通:放入parking_lot中的值,不是指向原对象的指针,而是指向原对象的副本的指针。
然后,采用一个约定,就是当我们释放parking_lot时,也释放其中所指向的全部对象。因此,把前面例子改为:
int num_vehicles = 0;
Automobile auto_mobile = /*...*/;
Parking_lot[num_vehicles++] = new Automobile(auto_mobile);
尽管这样修改可以不用存储指向本地对象的指针,但是它也带来了动态内存管理的负担。
另外,只有当我们知道要放到 parking_lot 中的对象是静态类型后,这种方法才能起作用。(这是为什么?)
//静态类型:一个对象(变量)的静态类型就是其声明类型,如表达式int a中的int就是对象a的声明类型,即静态类型;
//动态类型:一个对象(变量)的动态类型就是指程序执行过程中对象(指针或引用)实际所指对象的类型
Base* pB = new Drived;
// pB的静态类型就是 Base,动态类型就是 Drived
如果不知道会怎么样?例如,假设我们想让parking_lot[p]
指向一个新建的 Vehicle,这个 Vehicle 的类型和值与parking_lot[q]
指向的
对象相同,情况会怎么样?那么,我们将不可以使用以下的语句:
#include <iostream>
using namespace std;
class Vehicle {
public:
virtual double weight() const = 0;
virtual void start() = 0;
virtual ~Vehicle() {
cout << "virtual ~Vehicle()" << endl;
}
};
// 陆地车
class RoadVehicle : public Vehicle {
double weight() const override {
return 100;
}
void start() override {
cout << "RoadVehicle start()" << endl;
}
};
// 汽车
class Automobile : public RoadVehicle {
double weight() const override {
return 100;
}
void start() override {
cout << "Automobile start()" << endl;
}
};
int main () {
Vehicle* parking_lot[1000];
int p = 0, q = 1;
Automobile auto_mobile_p;
parking_lot[p] = new Automobile(auto_mobile_p);
Automobile auto_mobile_q;
parking_lot[q] = new Automobile(auto_mobile_q);
if(p != q) { // 不相等
delete parking_lot[p];
parking_lot[p] = parking_lot[q]; // 现在 parking_lot[p] 指向了 parking_lot[q]
}
cout << "parking_lot[p] = " << parking_lot[p] << endl; // 0x1d1790
cout << "parking_lot[q] = " << parking_lot[q] << endl; // 0x1d1790
}
因为接下来parking_lot[p] 和 parking_lot[q]将指向相同的对象;我们也不可以使用以下的语句:
if(p != q) {
delete parking_lot[p];
parking_lot[p] = new Vehicle(parking_lot[q]); // 没有Vehicle类型
}
5.3 虚复制函数
想一个办法:来复制编译时类型未知的对象
我们知道,c++中处理未知类型的对象的方法就是使用虚函数。这种函数的一个显而易见的名字就是copy or clone。
由于我们是想能够复制任何类型的Vehicle,所以应该在Vehicle种增加一个合适的纯虚函数:
class Vehicle {
public:
virtual double weight() const = 0; // 函数后加const,这些成员函数不改变类的数据成员,也就是说,这些函数是"只读"函数
virtual void start() = 0;
virtual Vehicle* copy() const = 0;
// ...
};
接下来,在每个派生自Vehicle的类中增加一个新的成员函数copy。
指导思想:如果vp指向某个继承自Vehicle的不确定类的对象,则vp->copy()会获得一个指针,该指针指向该对象的一个副本。
例如,如果Truck继承自(直接或间接)Vehicle,则它的copy函数类似于:
Vehicle* Truck::copy() const {
return new Truck(*this); // *this 对象的值
}
当然,处理完一个对象后,需要清除该对象。要做到这一点,Vehicle就必须有一个虚析构函数。
#include <iostream>
#include <memory>
using namespace std;
class Vehicle {
public:
Vehicle() {
cout << "Vehicle 构造函数" << endl;
}
virtual ~Vehicle() {
cout << "Vehicle 析构函数" << endl;
}
public:
virtual Vehicle* copy() const = 0;
virtual void say() const = 0;
};
class Truck : public Vehicle {
public:
Truck() {
cout << "Truck 构造函数" << endl;
}
~Truck() override {
cout << "Truck 析构函数" << endl;
}
public:
Vehicle* copy() const override {
cout << "Truck copy()" << endl;
return new Truck(*this);
}
void say() const override {
cout << "Truck say" << endl;
}
};
int main() {
auto vp = make_shared<Truck>();
auto vehicle = vp->copy();
vehicle->say();
}
5.4 定义代理类
有没有一种方法:既能使我们避免显示地处理内存分配,又能保持Vehicle类在运行时绑定的属性呢?
答案:定义一个行为和Vehicle对象相似、而又潜在地表示了所有继承自Vehicle类的对象的东西。我们把这种类的对象叫做 代理(surrogate)。
每个Vehicle代理都代表某个继承自Vehicle类的对象。只要该代理关联着这个对象,该对象肯定存在。因此,复制代理就会复制相对应的对象,而给代理赋新值也会先删除旧对象,在复制新对象。
class VehicleSurrogate {
public:
VehicleSurrogate() = default;
VehicleSurrogate(const Vehicle&);
~VehicleSurrogate() = default;
VehicleSurrogate(const VehicleSurrogate&);
VehicleSurrogate& operator=(const VehicleSurrogate&);
private:
Vehicle *vp;
}
- 上述代理类有一个以
const Vehicle&
为参数的构造函数,这样就可以为任何继承自 Vehicle类的对象创建代理了。 - 同时,代理类还有一个默认的构造函数,所以可以创建 VehicleSurrogate 对象的数组了。
默认的构造函数带来了新问题:
如果 Vehicle 是抽象基类,我们应该如何规定 VehicleSurrogate 的默认操作呢?
它所指向的对象的类型是什么呢?不可能是 Vehicle,因为根本没有 Vehicle对象。
引入空代理(empty surrogate)
为了有更好的办法,我们要引入行为类似零指针的 空代理(empty surrogate) 的概念。能够创建、销毁和复制这样的代理,
但是进行其他操作就视为出错。
ok,下面是成员函数的定义:
class VehicleSurrogate {
public:
VehicleSurrogate() : vp(nullptr) {
cout << "VehicleSurrogate 默认构造函数" << endl;
}
explicit VehicleSurrogate(const Vehicle& v) : vp(v.copy()) {
cout << "VehicleSurrogate(const Vehicle& v) " << endl;
}
~VehicleSurrogate() {
cout << "VehicleSurrogate 析构函数" << endl;
delete vp;
}
VehicleSurrogate(const VehicleSurrogate& v) : vp(v.vp ? v.vp->copy() : nullptr) {
cout << "VehicleSurrogate 复制构造函数" << endl;
}
VehicleSurrogate& operator=(const VehicleSurrogate& v){
if (this != &v) {
delete vp;
vp = (v.vp ? v.vp->copy() : nullptr);
}
return *this;
}
private:
Vehicle *vp;
};
3个技巧要注意:
-
每次对copy的调用都是一次虚拟调用。这些调用只能是虚拟的,别无选择,因为类Vehicle的对象不存在。
即使是在那个只接收一个 const Vehicle& 参数的复制构造函数中,它所进行的 v.copy调用也是一次虚拟调用,
因为v是一个引用,而不是一个普通对象。 -
注意 复制构造函数和赋值操作符中的 v.vp 非零的检测。这个检测是必须的,否则调用v.vp->copy() 时就会出错。
-
注意 对赋值操作符进行检测,确保没有把代理赋值给它的自身。
下面令该代理类支持 Vehicle类所能支持的其他操作。我们把start() 和 weight() 加入类VehicleSurrogate 中。
注意,VehicleSurrogate 类中的start 和weight 不是虚函数,我们使用的对象都是 VehicleSurrogate 类的对象;
没有继承自Vehicle对象。当然函数本身可以调用相应的 Vehicle对象的虚函数。
它们也应该检查确保 vp 不为零:
class VehicleSurrogate {
public:
VehicleSurrogate() = default;
VehicleSurrogate(const Vehicle&);
~VehicleSurrogate() = default;
VehicleSurrogate(const VehicleSurrogate&);
VehicleSurrogate& operator=(const VehicleSurrogate&);
public: // 类Vehicle支持的操作
double weight() const {
if (!vp)
throw "empty VehicleSurrogate.weight()";
return vp->weight();
}
void start() const {
if (!vp)
throw "empty VehicleSurrogate.start()";
return vp->start();
}
// ...
private:
Vehicle *vp;
}
接下来,我们就很容易定义我们的停车场了(parking_lot)
VehicleSurrogate parking_lot[5];
Truck x;
parking_lot[2] = VehicleSurrogate(x);
最后这个语句创建了一个关于对象x的副本,并将 VehicleSurrogate 对象绑定到该副本。
5.5 小结
将继承和容器共用,我们需要解决两个问题:
- 控制内存分配
- 把不同类型的对象放入同一个容器中
提出代理类:这个类的每个对象都代表另一个对象,该对象可以是位于一个完整继承层次中的
任何类的对象。
5.6 完整代码
#include <iostream>
#include <memory>
using namespace std;
class Vehicle {
public:
Vehicle() {
cout << "Vehicle 构造函数" << endl;
}
virtual ~Vehicle() {
cout << "Vehicle 析构函数" << endl;
}
public:
virtual Vehicle* copy() const = 0;
virtual void say() const = 0;
virtual double weight() const = 0;
virtual void start() const = 0;
};
class Truck : public Vehicle {
public:
Truck() {
cout << "Truck 构造函数" << endl;
}
~Truck() override {
cout << "Truck 析构函数" << endl;
}
public:
Vehicle* copy() const override {
cout << "Truck copy()" << endl;
return new Truck(*this);
}
void say() const override {
cout << "Truck say" << endl;
}
double weight() const override {
cout << "Truck weight" << endl;
}
void start() const override {
cout << "Truck start" << endl;
}
};
class VehicleSurrogate {
public:
VehicleSurrogate() : vp(nullptr) {
cout << "VehicleSurrogate 默认构造函数" << endl;
}
explicit VehicleSurrogate(const Vehicle& v) : vp(v.copy()) {
cout << "VehicleSurrogate(const Vehicle& v) " << endl;
}
~VehicleSurrogate() {
cout << "VehicleSurrogate 析构函数" << endl;
delete vp;
}
VehicleSurrogate(const VehicleSurrogate& v) : vp(v.vp ? v.vp->copy() : nullptr) {
cout << "VehicleSurrogate 复制构造函数" << endl;
}
VehicleSurrogate& operator=(const VehicleSurrogate& v){
cout << "VehicleSurrogate 赋值构造函数" << endl;
if (this != &v) {
delete vp;
vp = (v.vp ? v.vp->copy() : nullptr);
}
return *this;
}
double weight() const {
if (!vp)
throw "empty VehicleSurrogate.weight()";
return vp->weight();
}
void start() const {
if (!vp)
throw "empty VehicleSurrogate.start()";
return vp->start();
}
// ...
private:
Vehicle *vp;
};
int main() {
VehicleSurrogate parking_lot[5];
Truck x;
parking_lot[2] = VehicleSurrogate(x);
return 0;
}
// 输出
VehicleSurrogate 默认构造函数
VehicleSurrogate 默认构造函数
VehicleSurrogate 默认构造函数
VehicleSurrogate 默认构造函数
VehicleSurrogate 默认构造函数
Vehicle 构造函数
Truck 构造函数
Truck copy()
VehicleSurrogate(const Vehicle& v)
VehicleSurrogate 赋值构造函数
Truck copy()
VehicleSurrogate 析构函数
Truck 析构函数
Vehicle 析构函数
Truck 析构函数
Vehicle 析构函数
VehicleSurrogate 析构函数
VehicleSurrogate 析构函数
VehicleSurrogate 析构函数
Truck 析构函数
Vehicle 析构函数
VehicleSurrogate 析构函数
VehicleSurrogate 析构函数