📄 mi31.htm
字号:
(*phm)["SpaceShip"] =
reinterpret_cast<HitFunctionPtr>(&hitSpaceShip);
<A NAME="54503"></A>
(*phm)["SpaceStation"] =
reinterpret_cast<HitFunctionPtr>(&hitSpaceStation);
<A NAME="54504"></A>
(*phm)["Asteroid"] =
reinterpret_cast<HitFunctionPtr>(&hitAsteroid);
<A NAME="54492"></A>
return phm;
}
</PRE>
</UL>
<P><A NAME="dingp60"></A><A NAME="35032"></A>
This will compile, but it's a bad idea. It entails doing something you should never do: lying to your compilers. Telling them that <CODE>hitSpaceShip</CODE>, <CODE>hitSpaceStation</CODE>, and <CODE>hitAsteroid</CODE> are functions expecting a <CODE>GameObject</CODE> argument is simply not true. <CODE>hitSpaceShip</CODE> expects a <CODE>SpaceShip</CODE>, <CODE>hitSpaceStation</CODE> expects a <CODE>SpaceStation</CODE>, and <CODE>hitAsteroid</CODE> expects an <CODE>Asteroid</CODE>. The casts say otherwise. The casts <NOBR>lie.<SCRIPT>create_link(60);</SCRIPT>
</NOBR></P>
<P><A NAME="dingp61"></A><A NAME="53428"></A>
More than morality is on the line here. Compilers don't like to be lied to, and they often find a way to exact revenge when they discover they've been deceived. In this case, they're likely to get back at you by generating bad code for functions you call through <CODE>*phm</CODE> in cases where <CODE>GameObject</CODE>'s derived classes employ multiple inheritance or have virtual base classes. In other words, if <CODE>SpaceStation</CODE>, <CODE>SpaceShip</CODE>, or <CODE>Asteroid</CODE> had other base classes (in addition to <CODE>GameObject</CODE>), you'd probably find that your calls to collision-processing functions in <CODE>collide</CODE> would behave quite <NOBR>rudely.<SCRIPT>create_link(61);</SCRIPT>
</NOBR></P>
<P><A NAME="dingp62"></A><A NAME="53470"></A>
<A NAME="p242"></A>Consider again the A-B-C-D inheritance hierarchy and the possible object layout for a <CODE>D</CODE> object that is described in <a href="./MI24_FR.HTM#41284" TARGET="_top">Item 24</A>:<SCRIPT>create_link(62);</SCRIPT>
</P>
<SPAN ID="Image2of1" STYLE="position: absolute; z-index:1; visibility: hidden"><IMG SRC="./IMAGES/GRAPHICS/DIAGRAMS/I_242A1.GIF" BORDER=0></SPAN>
<SPAN ID="Image2of2" STYLE="position: absolute; z-index:1; visibility: hidden"><IMG SRC="./IMAGES/GRAPHICS/DIAGRAMS/I_242A2.GIF" BORDER=0></SPAN>
<SPAN ID="Image2of3" STYLE="position: absolute; z-index:1; visibility: hidden"><IMG SRC="./IMAGES/GRAPHICS/DIAGRAMS/I_242A3.GIF" BORDER=0></SPAN>
<SPAN ID="Image2of4" STYLE="position: absolute; z-index:1; visibility: hidden"><IMG SRC="./IMAGES/GRAPHICS/DIAGRAMS/I_242A4.GIF" BORDER=0></SPAN>
<SPAN ID="Image2of5" STYLE="position: absolute; z-index:1; visibility: hidden"><IMG SRC="./IMAGES/GRAPHICS/DIAGRAMS/I_242A5.GIF" BORDER=0></SPAN>
<SPAN ID="Image2of6" STYLE="position: relative; z-index:1; visibility: hidden"><IMG SRC="./IMAGES/GRAPHICS/DIAGRAMS/I_242A5.GIF" BORDER=0></SPAN>
<P><A NAME="dingp63"></A><A NAME="53536"></A>
Each of the four class parts in a <CODE>D</CODE> object has a different address. This is important, because even though pointers and references behave differently (see <a href="./MI1_FR.HTM#11029" TARGET="_top">Item 1</A>), compilers typically <I>implement</I> references by using pointers in the generated code. Thus, pass-by-reference is typically implemented by passing a pointer to an object. When an object with multiple base classes (such as a <CODE>D</CODE> object) is passed by reference, it is crucial that compilers pass the <I>correct</I> address — the one corresponding to the declared type of the parameter in the function being <NOBR>called.<SCRIPT>create_link(63);</SCRIPT>
</NOBR></P>
<P><A NAME="dingp64"></A><A NAME="53594"></A>
But what if you've lied to your compilers and told them your function expects a <CODE>GameObject</CODE> when it really expects a <CODE>SpaceShip</CODE> or a <CODE>SpaceStation</CODE>? Then they'll pass the wrong address when you call the function, and the resulting runtime carnage will probably be gruesome. It will also be <I>very</I> difficult to determine the cause of the problem. There are good reasons why casting is discouraged. This is one of <NOBR>them.<SCRIPT>create_link(64);</SCRIPT>
</NOBR></P>
<P><A NAME="dingp65"></A><A NAME="53619"></A>
Okay, so casting is out. Fine. But the type mismatch between the function pointers a <CODE>HitMap</CODE> is willing to contain and the pointers to the <CODE>hitSpaceShip</CODE>, <CODE>hitSpaceStation</CODE>, and <CODE>hitAsteroid</CODE> functions remains. There is only one way to resolve the conflict: change the types of the functions so they all take <CODE>GameObject</CODE> <NOBR>arguments:<SCRIPT>create_link(65);</SCRIPT>
</NOBR></P><A NAME="53629"></A>
<UL><PRE>class GameObject { // this is unchanged
public:
virtual void collide(GameObject& otherObject) = 0;
...
};
<A NAME="53620"></A>
<A NAME="p243"></A>class SpaceShip: public GameObject {
public:
virtual void collide(GameObject& otherObject);
<A NAME="53630"></A>
// these functions now all take a GameObject parameter
virtual void hitSpaceShip(GameObject& spaceShip);
virtual void hitSpaceStation(GameObject& spaceStation);
virtual void hitAsteroid(GameObject& asteroid);
...
};
</PRE>
</UL>
<P><A NAME="dingp66"></A><A NAME="53643"></A>
Our solution to the double-dispatching problem that was based on virtual functions overloaded the function name <CODE>collide</CODE>. Now we are in a position to understand why we didn't follow suit here — why we decided to use an associative array of member function pointers instead. All the <CODE>hit</CODE> functions take the same parameter type, so we must give them different <NOBR>names.<SCRIPT>create_link(66);</SCRIPT>
</NOBR></P>
<P><A NAME="dingp67"></A><A NAME="53743"></A>
Now we can write <CODE>initializeCollisionMap</CODE> the way we always wanted <NOBR>to:<SCRIPT>create_link(67);</SCRIPT>
</NOBR></P><A NAME="54527"></A>
<UL><PRE>SpaceShip::HitMap * SpaceShip::initializeCollisionMap()
{
HitMap *phm = new HitMap;
<A NAME="54528"></A>
(*phm)["SpaceShip"] = &hitSpaceShip;
(*phm)["SpaceStation"] = &hitSpaceStation;
(*phm)["Asteroid"] = &hitAsteroid;
<A NAME="54529"></A>
return phm;
}
</PRE>
</UL>
<P><A NAME="dingp68"></A><A NAME="53647"></A>
Regrettably, our <CODE>hit</CODE> functions now get a general <CODE>GameObject</CODE> parameter instead of the derived class parameters they expect. To bring reality into accord with expectation, we must resort to a <CODE>dynamic_cast</CODE> (see <a href="./MI2_FR.HTM#77216" TARGET="_top">Item 2</A>) at the top of each <NOBR>function:<SCRIPT>create_link(68);</SCRIPT>
</NOBR></P><A NAME="53621"></A>
<UL><PRE>void SpaceShip::hitSpaceShip(GameObject& spaceShip)
{
SpaceShip& otherShip=
dynamic_cast<SpaceShip&>(spaceShip);
<A NAME="53688"></A>
<I>process a SpaceShip-SpaceShip collision;</I>
<A NAME="53689"></A>
}
<A NAME="53691"></A>
void SpaceShip::hitSpaceStation(GameObject& spaceStation)
{
SpaceStation& station=
dynamic_cast<SpaceStation&>(spaceStation);
<A NAME="53706"></A>
<I>process a SpaceShip-SpaceStation collision;</I>
<A NAME="53707"></A>
}
<A NAME="53695"></A>
<A NAME="p244"></A>void SpaceShip::hitAsteroid(GameObject& asteroid)
{
Asteroid& theAsteroid =
dynamic_cast<Asteroid&>(asteroid);
<A NAME="53710"></A>
<I>process a SpaceShip-Asteroid collision;</I>
<A NAME="53711"></A>
}
</PRE>
</UL>
<P><A NAME="dingp69"></A><A NAME="53445"></A>
Each of the <CODE>dynamic_cast</CODE>s will throw a <CODE>bad_cast</CODE> exception if the cast fails. They should never fail, of course, because the <CODE>hit</CODE> functions should never be called with incorrect parameter types. Still, we're better off safe than <NOBR>sorry.<SCRIPT>create_link(69);</SCRIPT>
</NOBR></P>
<P><A NAME="dingp70"></A><font ID="mhtitle">Using Non-Member Collision-Processing Functions</font><SCRIPT>create_link(70);</SCRIPT>
</P>
<P><A NAME="dingp71"></A><A NAME="35054"></A>
We now know how to build a vtbl-like associative array that lets us implement the second half of a double-dispatch, and we know how to encapsulate the details of the associative array inside a lookup function. Because this array contains pointers to <I>member</I> functions, however, we still have to modify class definitions if a new type of <CODE>GameObject</CODE> is added to the game, and that means everybody has to recompile, even people who don't care about the new type of object. For example, if <CODE>Satellite</CODE> were added to our game, we'd have to augment the <CODE>SpaceShip</CODE> class with a declaration of a function to handle collisions between satellites and spaceships. All <CODE>SpaceShip</CODE> clients would then have to recompile, even if they couldn't care less about the existence of satellites. This is the problem that led us to reject the implementation of double-dispatching based purely on virtual functions, and that solution was a lot less work than the one we've just <NOBR>seen.<SCRIPT>create_link(71);</SCRIPT>
</NOBR></P>
<P><A NAME="dingp72"></A><A NAME="35933"></A>
The recompilation problem would go away if our associative array contained pointers to non-member functions. Furthermore, switching to non-member collision-processing functions would let us address a design question we have so far ignored, namely, in which class should collisions between objects of different types be handled? With the implementation we just developed, if object 1 and object 2 collide and object 1 happens to be the left-hand argument to <CODE>processCollision</CODE>, the collision will be handled inside the class for object 1. If object 2 happens to be the left-hand argument to <CODE>processCollision</CODE>, however, the collision will be handled inside the class for object 2. Does this make sense? Wouldn't it be better to design things so that collisions between objects of types A and B are handled by neither A nor B but instead in some neutral location outside both <NOBR>classes?<SCRIPT>create_link(72);</SCRIPT>
</NOBR></P> <P><A NAME="dingp73"></A><A NAME="35948"></A>
If we move the collision-processing functions out of our classes, we can give clients header files that contain class definitions without any <CODE>hit</CODE> or <CODE>collide</CODE> functions. We can then structure our implementation file for <CODE>processCollision</CODE> as <NOBR>follows:<SCRIPT>create_link(73);</SCRIPT>
</NOBR></P><A NAME="35986"></A>
<UL><PRE><A NAME="p245"></A>#include "SpaceShip.h"
#include "SpaceStation.h"
#include "Asteroid.h"
<A NAME="36017"></A>
namespace { // unnamed namespace — see below
<A NAME="36161"></A>
// primary collision-processing functions
void shipAsteroid(GameObject& spaceShip,
GameObject& asteroid);
<A NAME="53847"></A>
void shipStation(GameObject& spaceShip,
GameObject& spaceStation);
<A NAME="53848"></A>
void asteroidStation(GameObject& asteroid,
GameObject& spaceStation);
...
<A NAME="36165"></A>
// secondary collision-processing functions that just
// implement symmetry: swap the parameters and call a
// primary function
void asteroidShip(GameObject& asteroid,
GameObject& spaceShip)
{ shipAsteroid(spaceShip, asteroid); }
<A NAME="35990"></A>
void stationShip(GameObject& spaceStation,
GameObject& spaceShip)
{ shipStation(spaceShip, spaceStation); }
<A NAME="35991"></A>
void stationAsteroid(GameObject& spaceStation,
GameObject& asteroid)
{ asteroidStation(asteroid, spaceStation); }
<A NAME="35994"></A>
...
<A NAME="54571"></A>
// see below for a description of these types/functions
typedef void (*HitFunctionPtr)(GameObject&, GameObject&);
typedef map< pair<string,string>, HitFunctionPtr > HitMap;
<A NAME="54566"></A>
pair<string,string> makeStringPair(const char *s1,
const char *s2);
<A NAME="54615"></A>
HitMap * initializeCollisionMap();
<A NAME="36013"></A>
HitFunctionPtr lookup(const string& class1,
const string& class2);
<A NAME="35998"></A>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -