📄 mi31.htm
字号:
} // end namespace
<A NAME="36030"></A>
void processCollision(GameObject& object1,
GameObject& object2)
{
HitFunctionPtr phf = lookup(typeid(object1).name(),
typeid(object2).name());
<A NAME="36102"></A>
if (phf) phf(object1, object2);
else throw UnknownCollision(object1, object2);
}
</PRE>
</UL>
<P><A NAME="dingp74"></A><A NAME="36044"></A>
<A NAME="p246"></A>Note the use of the unnamed namespace to contain the functions used to implement <CODE>processCollision</CODE>. Everything in such an unnamed namespace is private to the current translation unit (essentially the current file) — it's just like the functions were declared <CODE>static</CODE> at file scope. With the advent of namespaces, however, statics at file scope have been deprecated, so you should accustom yourself to using unnamed namespaces as soon as your compilers support <NOBR>them.<SCRIPT>create_link(74);</SCRIPT>
</NOBR></P>
<P><A NAME="dingp75"></A><A NAME="36053"></A>
Conceptually, this implementation is the same as the one that used member functions, but there are some minor differences. First, <CODE>HitFunctionPtr</CODE> is now a typedef for a pointer to a non-member function. Second, the exception class <CODE>CollisionWithUnknownObject</CODE> has been renamed <CODE>UnknownCollision</CODE> and modified to take two objects instead of one. Finally, <CODE>lookup</CODE> must now take two type names and perform both parts of the double-dispatch. This means our collision map must now hold three pieces of information: two types names and a <CODE>HitFunctionPtr</CODE>.<SCRIPT>create_link(75);</SCRIPT>
</P> <P><A NAME="dingp76"></A><A NAME="54600"></A>
As fate would have it, the standard <CODE>map</CODE> class is defined to hold only two pieces of information. We can finesse that problem by using the standard <CODE>pair</CODE> template, which lets us bundle the two type names together as a single object. <CODE>initializeCollisionMap</CODE>, along with its <CODE>makeStringPair</CODE> helper function, then looks like <NOBR>this:<SCRIPT>create_link(76);</SCRIPT>
</NOBR></P><A NAME="7536"></A>
<UL><PRE>// we use this function to create pair<string,string>
// objects from two char* literals. It's used in
// initializeCollisionMap below. Note how this function
// enables the return value optimization (see <a href="./MI20_FR.HTM#45310" TARGET="_top">Item 20</A>).
<A NAME="7541"></A>
namespace { // unnamed namespace again — see below
<A NAME="6636"></A>
pair<string,string> makeStringPair(const char *s1,
const char *s2)
{ return pair<string,string>(s1, s2); }
<A NAME="6647"></A>
} // end namespace
<A NAME="54660"></A>
namespace { // still the unnamed namespace — see below
<A NAME="6648"></A>
HitMap * initializeCollisionMap()
{
HitMap *phm = new HitMap;
<A NAME="54655"></A>
(*phm)[makeStringPair("SpaceShip","Asteroid")] =
&shipAsteroid;
<A NAME="54675"></A>
(*phm)[makeStringPair("SpaceShip", "SpaceStation")] =
&shipStation;
<A NAME="54656"></A>
...
<A NAME="54610"></A>
return phm;
}
<A NAME="6649"></A>
} // end namespace
</PRE>
</UL>
<P><A NAME="dingp77"></A><A NAME="6617"></A>
<CODE><A NAME="p247"></A>lookup</CODE> must also be modified to work with the <CODE>pair<string, string></CODE> objects that now comprise the first component of the collision <NOBR>map:<SCRIPT>create_link(77);</SCRIPT>
</NOBR></P><A NAME="6651"></A>
<UL><PRE>namespace { // I explain this below — trust me
<A NAME="6646"></A>
HitFunctionPtr lookup(const string& class1,
const string& class2)
{
static auto_ptr<HitMap>
collisionMap(initializeCollisionMap());
<A NAME="54078"></A>
// see below for a description of make_pair
HitMap::iterator mapEntry=
collisionMap->find(make_pair(class1, class2));
<A NAME="53886"></A>
if (mapEntry == collisionMap->end()) return 0;
<A NAME="53905"></A>
return (*mapEntry).second;
}
<A NAME="6657"></A>
} // end namespace
</PRE>
</UL>
<P><A NAME="dingp78"></A><A NAME="54085"></A>
This is almost exactly what we had before. The only real difference is the use of the <CODE>make_pair</CODE> function in this <NOBR>statement:<SCRIPT>create_link(78);</SCRIPT>
</NOBR></P><A NAME="54704"></A>
<UL><PRE>HitMap::iterator mapEntry=
collisionMap->find(make_pair(class1, class2));
</PRE>
</UL>
<P><A NAME="dingp79"></A><A NAME="54091"></A>
<CODE>make_pair</CODE> is just a convenience function (template) in the standard library (see <a href="../EC/EI49_FR.HTM#8392" TARGET="_top">Item E49</A> and <a href="./MI35_FR.HTM#5473" TARGET="_top">Item 35</A>) that saves us the trouble of specifying the types when constructing a <CODE>pair</CODE> object. We could just as well have written the statement like <NOBR>this:<SCRIPT>create_link(79);</SCRIPT>
</NOBR></P>
<A NAME="54103"></A>
<UL><PRE>HitMap::iterator mapEntry=
collisionMap->find(pair<string,string>(class1, class2));
</PRE>
</UL>
<P><A NAME="dingp80"></A><A NAME="54101"></A>
This calls for more typing, however, and specifying the types for the <CODE>pair</CODE> is redundant (they're the same as the types of <CODE>class1</CODE> and <CODE>class2</CODE>), so the <CODE>make_pair</CODE> form is more commonly <NOBR>used.<SCRIPT>create_link(80);</SCRIPT>
</NOBR></P>
<P><A NAME="dingp81"></A><A NAME="6641"></A>
Because <CODE>makeStringPair</CODE>, <CODE>initializeCollisionMap</CODE>, and <CODE>lookup</CODE> were declared inside an unnamed namespace, each must be implemented within the same namespace. That's why the implementations of the functions above are in the unnamed namespace (for the same translation unit as their declarations): so the linker will correctly associate their definitions (i.e., their implementations) with their earlier <NOBR>declarations.<SCRIPT>create_link(81);</SCRIPT>
</NOBR></P> <P><A NAME="dingp82"></A><A NAME="54086"></A>
We have finally achieved our goals. If new subclasses of <CODE>GameObject</CODE> are added to our hierarchy, existing classes need not recompile (unless they wish to use the new classes). We have no tangle of RTTI-based <CODE>switch</CODE> or <CODE>if</CODE>-<CODE>then</CODE>-<CODE>else</CODE> conditionals to maintain. The addition of new classes to the hierarchy requires only well-defined and localized <A NAME="p248"></A>changes to our system: the addition of one or more map insertions in <CODE>initializeCollisionMap</CODE> and the declarations of the new collision-processing functions in the unnamed namespace associated with the implementation of <CODE>processCollision</CODE>. It may have been a lot of work to get here, but at least the trip was worthwhile. Yes? <NOBR>Yes?<SCRIPT>create_link(82);</SCRIPT>
</NOBR></P> <P><A NAME="dingp83"></A><A NAME="53983"></A>
<NOBR>Maybe.<SCRIPT>create_link(83);</SCRIPT>
</NOBR></P>
<P><A NAME="dingp84"></A><font ID="mhtitle">Inheritance and Emulated Virtual Function Tables</font><SCRIPT>create_link(84);</SCRIPT>
</P>
<P><A NAME="dingp85"></A><A NAME="8146"></A>
There is one final problem we must confront. (If, at this point, you are wondering if there will <I>always</I> be one final problem to confront, you have truly come to appreciate the difficulty of designing an implementation mechanism for virtual functions.) Everything we've done will work fine as long as we never need to allow inheritance-based type conversions when calling collision-processing functions. But suppose we develop a game in which we must sometimes distinguish between commercial space ships and military space ships. We could modify our hierarchy as follows, where we've heeded the guidance of <a href="./MI33_FR.HTM#10947" TARGET="_top">Item 33</A> and made the concrete classes <CODE>CommercialShip</CODE> and <CODE>MilitaryShip</CODE> inherit from the newly abstract class <CODE>SpaceShip</CODE>:<SCRIPT>create_link(85);</SCRIPT>
</P>
<SPAN ID="Image3of1" STYLE="position: absolute; z-index:1; visibility: hidden"><IMG SRC="./IMAGES/GRAPHICS/DIAGRAMS/I_248A1.GIF" BORDER=0></SPAN>
<SPAN ID="Image3of2" STYLE="position: absolute; z-index:1; visibility: hidden"><IMG SRC="./IMAGES/GRAPHICS/DIAGRAMS/I_248A2.GIF" BORDER=0></SPAN>
<SPAN ID="Image3of3" STYLE="position: absolute; z-index:1; visibility: hidden"><IMG SRC="./IMAGES/GRAPHICS/DIAGRAMS/I_248A3.GIF" BORDER=0></SPAN>
<SPAN ID="Image3of4" STYLE="position: absolute; z-index:1; visibility: hidden"><IMG SRC="./IMAGES/GRAPHICS/DIAGRAMS/I_248A4.GIF" BORDER=0></SPAN>
<SPAN ID="Image3of5" STYLE="position: absolute; z-index:1; visibility: hidden"><IMG SRC="./IMAGES/GRAPHICS/DIAGRAMS/I_248A5.GIF" BORDER=0></SPAN>
<SPAN ID="Image3of6" STYLE="position: relative; z-index:1; visibility: hidden"><IMG SRC="./IMAGES/GRAPHICS/DIAGRAMS/I_248A5.GIF" BORDER=0></SPAN>
<P><A NAME="dingp86"></A><A NAME="36393"></A>
Suppose commercial and military ships behave identically when they collide with something. Then we'd expect to be able to use the same collision-processing functions we had before <CODE>CommercialShip</CODE> and <CODE>MilitaryShip</CODE> were added. In particular, if a <CODE>MilitaryShip</CODE> object and an <CODE>Asteroid</CODE> collided, we'd <NOBR>expect<SCRIPT>create_link(86);</SCRIPT>
</NOBR></P><A NAME="36411"></A>
<UL><PRE>void shipAsteroid(GameObject& spaceShip,
GameObject& asteroid);
</PRE>
</UL>
<P><A NAME="dingp87"></A><A NAME="36425"></A>
to be called. It would not be. Instead, an <CODE>UnknownCollision</CODE> exception would be thrown. That's because <CODE>lookup</CODE> would be asked to find a function corresponding to the type names "MilitaryShip" and "Asteroid," and no such function would be found in <CODE>collisionMap</CODE>. Even <A NAME="p249"></A>though a <CODE>MilitaryShip</CODE> can be treated like a <CODE>SpaceShip</CODE>, <CODE>lookup</CODE> has no way of knowing <NOBR>that.<SCRIPT>create_link(87);</SCRIPT>
</NOBR></P> <P><A NAME="dingp88"></A><A NAME="36438"></A>
Furthermore, there is no easy way of telling it. If you need to implement double-dispatching and you need to support inheritance-based parameter conversions such as these, your only practical recourse is to fall back on the double-virtual-function-call mechanism we examined earlier. That implies you'll also have to put up with everybody recompiling when you add to your inheritance hierarchy, but that's just the way life is <NOBR>sometimes.<SCRIPT>create_link(88);</SCRIPT>
</NOBR></P>
<P><A NAME="dingp89"></A><font ID="mhtitle">Initializing Emulated Virtual Function Tables (Reprise)</font><SCRIPT>create_link(89);</SCRIPT>
</P>
<P><A NAME="dingp90"></A><A NAME="36442"></A>
That's really all there is to say about double-dispatching, but it would be unpleasant to end the discussion on such a downbeat note, and unpleasantness is, well, unpleasant. Instead, let's conclude by outlining an alternative approach to initializing <CODE>collisionMap</CODE>.<SCRIPT>create_link(90);</SCRIPT>
</P>
<P><A NAME="dingp91"></A><A NAME="54139"></A>
As things stand now, our design is entirely static. Once we've registered a function for processing collisions between two types of objects, that's it; we're stuck with that function forever. What if we'd like to add, remove, or change collision-processing functions as the game proceeds? There's no way to do <NOBR>it.<SCRIPT>create_link(91);</SCRIPT>
</NOBR></P> <P><A NAME="dingp92"></A><A NAME="54159"></A>
But there can be. We can turn the concept of a map for storing collision-processing functions into a class that offers member functions allowing us to modify the contents of the map dynamically. For <NOBR>example:<SCRIPT>create_link(92);</SCRIPT>
</NOBR></P>
<A NAME="54174"></A>
<UL><PRE>class CollisionMap {
public:
typedef void (*HitFunctionPtr)(GameObject&, GameObject&);
<A NAME="54167"></A>
void addEntry(const string& type1,
const string& type2,
HitFunctionPtr collisionFunction,
bool symmetric = true); // see below
<A NAME="54177"></A>
void removeEntry(const string& type1,
const string& type2);
<A NAME="54187"></A>
HitFunctionPtr lookup(const string& type1,
const string& type2);
<A NAME="54168"></A>
// this function returns a reference to the one and only
// map — see <a href="./MI26_F
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -