虚函数实现了一个单一调度,这只是我们所需要的一半;编译器为我们实现虚函数,
public:
virtual void collide(GameObject& otherObject) = 0;
...
public:
virtual void collide(GameObject& otherObject);
...
// throw an exception of this type:
class CollisionWithUnknownObject {
public:
CollisionWithUnknownObject(GameObject& whatWeHit);
...
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<Asteroid&>(otherObject);
process a SpaceShip-Asteroid collision;
}
else {
throw CollisionWithUnknownObject(otherObject);
}
判断。我们现在处于SpaceShip的成员函数中,所以*this肯定是一个SpaceShip对象,因此我们只需找出otherObject的类型。 这儿的代码一点都不复杂。它很容易实现。也很容易让它工作。RTTI只有一点令人不安:它只是看起来无害。实际的危险来自于最后一个else语句,在这儿抛了一个异常。 我们的代价是几乎放弃了封装,因为每个collide函数都必须知道所以其它同胞类中的版本。尤其是,如果增加一个新的类时,我们必须更新每一个基于RTTI的if...then...else链以处理这个新的类型。即使只是忘了一处,程序都将有一个bug,而且它还不显眼。 编译器也没办法帮助我们检查这种疏忽, 因为它们根本不知道我们在做什么 , 这种类型相关的程序在C语言中已经很有一段历史了,而我们也知道,这样的程序本质上是没有可维护性的。扩充这样的程序最终是不可想象的。这是引入虚函数的主意原因:将产生和维护类型相关的函数调用的担子由程序员转给编译器。当我们用RTTI实现二重调度时,我们正退回到过去的苦日子中。
所以我们在GameObject中申明一个虚函数collide。这个函数被派生类以通常的形式重载:
public:
virtual void collide(GameObject& otherObject) = 0;
...
};
public:
virtual void collide(GameObject& otherObject);
...
};
的。
种刺眼的体系下,我们首先是发现otherObject的真实类型,然后测试所有的可能:
// if we collide with an object of unknown type, we
// throw an exception of this type:
class CollisionWithUnknownObject {
public:
CollisionWithUnknownObject(GameObject& whatWeHit);
...
};
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<Asteroid&>(otherObject);
process a SpaceShip-Asteroid collision;
}
else {
throw CollisionWithUnknownObject(otherObject);
}
}
判断。我们现在处于SpaceShip的成员函数中,所以*this肯定是一个SpaceShip对象,因此我们只需找出otherObject的类型。 这儿的代码一点都不复杂。它很容易实现。也很容易让它工作。RTTI只有一点令人不安:它只是看起来无害。实际的危险来自于最后一个else语句,在这儿抛了一个异常。 我们的代价是几乎放弃了封装,因为每个collide函数都必须知道所以其它同胞类中的版本。尤其是,如果增加一个新的类时,我们必须更新每一个基于RTTI的if...then...else链以处理这个新的类型。即使只是忘了一处,程序都将有一个bug,而且它还不显眼。 编译器也没办法帮助我们检查这种疏忽, 因为它们根本不知道我们在做什么 , 这种类型相关的程序在C语言中已经很有一段历史了,而我们也知道,这样的程序本质上是没有可维护性的。扩充这样的程序最终是不可想象的。这是引入虚函数的主意原因:将产生和维护类型相关的函数调用的担子由程序员转给编译器。当我们用RTTI实现二重调度时,我们正退回到过去的苦日子中。
这种过时的技巧在C语言中导致了错误,它们C++语言也仍然导致错误。认识到我们自己的脆弱,我们在collide函数中加上了最后的那个else语句,以处理如果遇到一个未知
类型。这种情况原则上说是不可能发生的,但在我们决定使用RTTI时又怎么知道呢?有很多种方法来处理这种未曾预料的相互作用,但没有一个令人非常满意。在这个例子里,我们选择了抛出一个异常,但无法想象调用者对这个错误的处理能够比我们好多少,因为我们遇到了一个我们不知道其存在的东西。