30、proxy classes(代理类)
可以用两个类来实现二维数组:Array1D是一个一维数组,而Array2D则是一个Array1D的一维数组。Array1D的实例扮演的是一个在概念上不存在的一维数组,它是一个代理类。
代理类最神奇的功能是区分通过operator[]进行的是读操作还是写操作,它的思想是对于operator[]操作,返回的不是真正的对象,而是一个 proxy类,这个代理类记录了对象的信息。将它作为赋值操作的目标时,proxy类扮演的是左值;用其它方式使用它时,proxy类扮演的是右值。用赋值操作符来实现左值操作,用隐式类型转换来实现右值操作。
用proxy类区分operator[]作左值还是右值的局限性:要实现proxy类和原类型的无缝替代,必须声明原类型的一整套操作符;另外,使用proxy类还有隐式类型转换的所有缺点。
31、让函数根据一个以上的对象类型来决定如何虚化
假设正在编写一个小游戏,游戏的背景是发生在太空,有宇宙飞船、太空船和小行星,它们可能会互相碰撞,而且其碰撞的规则不同,如何用C++代码处理物体间的碰撞。代码的框架如下:
class GameObject{...};
class SpaceShip:public GameObject{...};
class SpaceStation:public GameObject{...};
class Asteroid:public GameObject{...};
void checkForCollision(GameObject& obj1,GameObject& obj2)
{
if(theyJustCollided(obj1,obj2))
{
processCollision(obj1,obj2);
}
else
{
...
}
}
当调用processCollision()时,obj1和obj2的碰撞结果取决于obj1和obj2的真实类型,但我们只知道它们是GameObject对象。相当于我们需要一种作用在多个对象上的虚函数。这类型问题,在C++中被称为二重调度问题,下面介绍几种方法解决二重调度问题。
1、虚函数+RTTI
虚函数实现了一个单一调度,我们只需要实现另一调度。其具体实现方法:将processCollision()定义为虚函数,解决一重调度,然后只需要检测一个对象类型,利用RTTI来检测对象的类型,再利用if…else语句来调用不同的处理方法。具体实现如下:
class GameObject{
public:
virtual void collide(GameObject& otherObject) = 0;
...
};
class SpaceShip:public GameObject{
public:
virtual void collide(GameObject& otherObject);
...
};
class CollisionWithUnknownObject{
public:
CollisionWithUnknownObject(GameObject& whatWehit);
...
};
void SpaceShip::collide(GameObject& otherObject)
{
const type_info& objectType = typeid(otherObject);
if(objectType == typeid(SpaceShip))
{
SpaceShip& ss = static_cast<SpaceShip&>(otherObject);
process a SpaceShip-SpaceShip collision;
}
else if(objectType == typeid(SpaceStation))
{
SpaceStation& ss = static_cast<SpaceStation&>(otherObject);
process a SpaceShip-SpaceStation collision;
}
else if(objectType == typeid(Asteroid))
{
Asteroid& a = static_cast<Asteriod&>(otherObject);
process a SpaceShip-Asteroid collision;
}
else
{
throw CollisionWithUnknownObject(otherObject);
}
}
该方法的实现简单,容易理解,其缺点是其扩展性不好。如果增加一个新的类时,我们必须更新每一个基于RTTI的if…else链,以处理这个新的类型。
2、只使用虚函数
基本原理就是用两个单一调度实现二重调度,也就是有两个单独的虚函数调用:第一次决定第一个对象的动态类型,第二次决定第二个对象动态类型。这种方法的缺陷仍然是:每个类必须知道它的所有同胞类,增加新类时,所有代码必须更新。
其具体实现如下:
class SpaceShip;
class SpaceStation;
class Asteroid;
class GameObject{
public:
virtual void collide(GameObject& otherObject) = 0;
virtual void collide(SpaceShip& otherObject) = 0;
virtual void collide(SpaceStation& otherObject) = 0;
virtual void collide(Asteroid& otherObject) = 0;
...
};
class SpaceShip:public GameObject{
public:
virtual void collide(GameObject& otherObject);
virtual void collide(SpaceShip& otherObject);
virtual void collide(SpaceStation& otherObject);
virtual void collide(Asteroid& otherObject);
...
};
void SpaceShip::collide(GameObject& otherObject)
{
otherObject.collide(*this);
}
void SpaceShip::collide(SpaceShip& otherObject)
{
process a SpaceShip-SpaceShip collision;
}
void SpaceShip::collide(SpaceStation& otherObject)
{
process a SpaceShip-SpaceStation collision;
}
void SpaceShip::collide(Asteroid& otherObject)
{
process a SpaceShip-Asteroid collision;
}
3、模拟虚函数表
在类外建立一张模拟虚函数表,该表是类型和函数指针的映射,加入新类型时不需改动其它类代码,只需在类外增加一个处理函数即可。