⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 mi31.htm

📁 有基本了解的程序员或程序爱好者而做
💻 HTM
📖 第 1 页 / 共 5 页
字号:
<A NAME="p237"></A>The syntax of function pointers is never very pretty, and for member function pointers it's worse than usual, so we've <CODE>typedef</CODE>ed <CODE>HitFunctionPtr</CODE> to be shorthand for a pointer to a member function of <CODE>SpaceShip</CODE> that takes a <CODE>GameObject&amp;</CODE> and returns <NOBR>nothing.<SCRIPT>create_link(41);</SCRIPT>
</NOBR></P>

<P><A NAME="dingp42"></A><A NAME="35165"></A>
Once we've got <CODE>lookup</CODE>, implementation of <CODE>collide</CODE> becomes the proverbial piece of <NOBR>cake:<SCRIPT>create_link(42);</SCRIPT>
</NOBR></P><A NAME="36187"></A>
<UL><PRE>void SpaceShip::collide(GameObject&amp; otherObject)
{
  HitFunctionPtr hfp =
    lookup(otherObject);                // find the function to call
<A NAME="36196"></A>
  if (hfp) {                            // if a function was found
    (this-&gt;*hfp)(otherObject);          // call it
  }
  else {
    throw CollisionWithUnknownObject(otherObject);
  }
}
</PRE>
</UL>
<P><A NAME="dingp43"></A><A NAME="36214"></A>
Provided we've kept the contents of our associative array in sync with the class hierarchy under <CODE>GameObject</CODE>, <CODE>lookup</CODE> must always find a valid function pointer for the object we pass it. People are people, however, and mistakes have been known to creep into even the most carefully crafted software systems. That's why we still check to make sure a valid pointer was returned from <CODE>lookup</CODE>, and that's why we still throw an exception if the impossible occurs and the lookup <NOBR>fails.<SCRIPT>create_link(43);</SCRIPT>
</NOBR></P>    <P><A NAME="dingp44"></A><A NAME="35750"></A>
All that remains now is the implementation of <CODE>lookup</CODE>. Given an associative array that maps from object types to member function pointers, the lookup itself is easy, but creating, initializing, and destroying the associative array is an interesting problem of its <NOBR>own.<SCRIPT>create_link(44);</SCRIPT>
</NOBR></P>    <P><A NAME="dingp45"></A><A NAME="35206"></A>
Such an array should be created and initialized before it's used, and it should be destroyed when it's no longer needed. We could use <CODE>new</CODE> and <CODE>delete</CODE> to create and destroy the array manually, but that would be error-prone: how could we guarantee the array wasn't used before we got around to initializing it? A better solution is to have compilers automate the process, and we can do that by making the associative array static in <CODE>lookup</CODE>. That way it will be created and initialized the first time <CODE>lookup</CODE> is called, and it will be automatically destroyed sometime after <CODE>main</CODE> is exited (see <a href="../EC/EI47_FR.HTM#8299" TARGET="_top">Item E47</A>).<SCRIPT>create_link(45);</SCRIPT>
</P>    <P><A NAME="dingp46"></A><A NAME="35259"></A>
Furthermore, we can use the <CODE>map</CODE> template from the Standard Template Library (see <a href="./MI35_FR.HTM#5473" TARGET="_top">Item 35</A>) as the associative array, because that's what a <CODE>map</CODE> <NOBR>is:<SCRIPT>create_link(46);</SCRIPT>
</NOBR></P><A NAME="35559"></A>
<UL><PRE><A NAME="p238"></A>class SpaceShip: public GameObject {
private:
  typedef void (SpaceShip::*HitFunctionPtr)(GameObject&amp;);
  typedef map&lt;string, HitFunctionPtr&gt; HitMap;
  ...
};
<A NAME="7501"></A>
SpaceShip::HitFunctionPtr
SpaceShip::lookup(const GameObject&amp; whatWeHit)
{
  static HitMap collisionMap;
  ...
}
</PRE>
</UL>
<P><A NAME="dingp47"></A><A NAME="35208"></A>
Here, <CODE>collisionMap</CODE> is our associative array. It maps the name of a class (as a <CODE>string</CODE> object) to a <CODE>SpaceShip</CODE> member function pointer. Because <CODE>map&lt;string,</CODE> <CODE>HitFunctionPtr&gt;</CODE> is quite a mouthful, we use a typedef to make it easier to swallow. (For fun, try writing the declaration of <CODE>collisionMap</CODE> without using the <CODE>HitMap</CODE> and <CODE>HitFunctionPtr</CODE> typedefs. Most people will want to do this only <NOBR>once.)<SCRIPT>create_link(47);</SCRIPT>
</NOBR></P>    <P><A NAME="dingp48"></A><A NAME="35278"></A>
Given <CODE>collisionMap</CODE>, the implementation of <CODE>lookup</CODE> is rather anticlimactic. That's because searching for something is an operation directly supported by the <CODE>map</CODE> class, and the one member function we can always (portably) call on the result of a <CODE>typeid</CODE> invocation is <CODE>name</CODE> (which, predictably<a href="#10808"><sup>11</sup></A>, yields the name of the object's <A HREF="./MIINTRFR.HTM#72671" TARGET="_top">dynamic type</a>). To implement <CODE>lookup</CODE>, then, we just find the entry in <CODE>collisionMap</CODE> corresponding to the dynamic type of <CODE>lookup</CODE>'s <NOBR>argument.<SCRIPT>create_link(48);</SCRIPT>
</NOBR></P>    <P><A NAME="dingp49"></A><A NAME="53277"></A>
The code for <CODE>lookup</CODE> is straightforward, but if you're not familiar with the Standard Template Library (again, see <a href="./MI35_FR.HTM#5473" TARGET="_top">Item 35</A>), it may not seem that way. Don't worry. The comments in the function explain what's going <NOBR>on.<SCRIPT>create_link(49);</SCRIPT>
</NOBR></P><A NAME="53187"></A>

<UL><PRE>SpaceShip::HitFunctionPtr
SpaceShip::lookup(const GameObject&amp; whatWeHit)
{
  static HitMap collisionMap;        // we'll see how to
                                     // initialize this below
<A NAME="53305"></A>
  // look up the collision-processing function for the type
  // of whatWeHit. The value returned is a pointer-like
  // object called an "iterator" (see <a href="./MI35_FR.HTM#5473" TARGET="_top">Item 35</A>).
  HitMap::iterator mapEntry=
    collisionMap.find(typeid(whatWeHit).name());
<A NAME="53188"></A>
  // mapEntry == collisionMap.end() if the lookup failed;
  // this is standard map behavior. Again, see <a href="./MI35_FR.HTM#5473" TARGET="_top">Item 35</A>.
  if (mapEntry == collisionMap.end()) return 0;
<A NAME="53353"></A>
<A NAME="p239"></A>  // If we get here, the search succeeded. mapEntry
  // points to a complete map entry, which is a
  // (string, HitFunctionPtr) pair. We want only the
  // second part of the pair, so that's what we return.
  return (*mapEntry).second;
}
</PRE>
</UL>
<P><A NAME="dingp50"></A><A NAME="81441"></A>
The final statement in the function returns <CODE>(*mapEntry).second</CODE> instead of the more conventional <CODE>mapEntry-&gt;second</CODE> in order to satisfy the vagaries of the STL. For details, see <a href="./MI18_FR.HTM#p96" TARGET="_top">page 96</A>.<SCRIPT>create_link(50);</SCRIPT>
</P>

<P><A NAME="dingp51"></A><font ID="mhtitle">Initializing Emulated Virtual Function Tables</font><SCRIPT>create_link(51);</SCRIPT>
</P>

<P><A NAME="dingp52"></A><A NAME="35311"></A>
Which brings us to the initialization of <CODE>collisionMap</CODE>. We'd like to say something like <NOBR>this,<SCRIPT>create_link(52);</SCRIPT>
</NOBR></P><A NAME="35328"></A>
<UL><PRE>// An incorrect implementation
SpaceShip::HitFunctionPtr
SpaceShip::lookup(const GameObject&amp; whatWeHit)
{
  static HitMap collisionMap;
<A NAME="35351"></A>
  collisionMap["SpaceShip"] = &amp;hitSpaceShip;
  collisionMap["SpaceStation"] = &amp;hitSpaceStation;
  collisionMap["Asteroid"] = &amp;hitAsteroid;
<A NAME="35330"></A>
  ...
<A NAME="35014"></A>
}
</PRE>
</UL>

<P><A NAME="dingp53"></A><A NAME="35015"></A>
but this inserts the member function pointers into collisionMap <I>each time</I> <CODE>lookup</CODE> is called, and that's needlessly inefficient. In addition, this won't compile, but that's a secondary problem we'll address <NOBR>shortly.<SCRIPT>create_link(53);</SCRIPT>
</NOBR></P>    <P><A NAME="dingp54"></A><A NAME="35377"></A>
What we need now is a way to put the member function pointers into <CODE>collisionMap</CODE> only once &#151; when <CODE>collisionMap</CODE> is created. That's easy enough to accomplish; we just write a private static member function called <CODE>initializeCollisionMap</CODE> to create and initialize our <CODE>map</CODE>, then we initialize <CODE>collisionMap</CODE> with <CODE>initializeCollisionMap</CODE>'s return <NOBR>value:<SCRIPT>create_link(54);</SCRIPT>
</NOBR></P><A NAME="54448"></A>
<UL><PRE>class SpaceShip: public GameObject {
private:
  static HitMap initializeCollisionMap();
  ...
<A NAME="7522"></A>
};
<A NAME="35386"></A>
SpaceShip::HitFunctionPtr
SpaceShip::lookup(const GameObject&amp; whatWeHit)
{
  static HitMap collisionMap = initializeCollisionMap();
  ...
}
</PRE>
</UL>

<P><A NAME="dingp55"></A><A NAME="35404"></A>
<A NAME="p240"></A>But this means we may have to pay the cost of copying the <CODE>map</CODE> object returned from <CODE>initializeCollisionMap</CODE> into <CODE>collisionMap</CODE> (see Items <a href="./MI19_FR.HTM#41177" TARGET="_top">19</A> and <a href="./MI20_FR.HTM#45310" TARGET="_top">20</A>). We'd prefer not to do that. We wouldn't have to pay if <CODE>initializeCollisionMap</CODE> returned a pointer, but then we'd have to worry about making sure the <CODE>map</CODE> object the pointer pointed to was destroyed at an appropriate <NOBR>time.<SCRIPT>create_link(55);</SCRIPT>
</NOBR></P>    <P><A NAME="dingp56"></A><A NAME="35415"></A>
Fortunately, there's a way for us to have it all. We can turn <CODE>collisionMap</CODE> into a smart pointer (see <a href="./MI28_FR.HTM#61766" TARGET="_top">Item 28</A>) that automatically deletes what it points to when the pointer itself is destroyed. In fact, the standard C++ library contains a template, <CODE>auto_ptr</CODE>, for just such a smart pointer (see <a href="./MI9_FR.HTM#5292" TARGET="_top">Item 9</A>). By making <CODE>collisionMap</CODE> a static <CODE>auto_ptr</CODE> in <CODE>lookup</CODE>, we can have <CODE>initializeCollisionMap</CODE> return a pointer to an initialized <CODE>map</CODE> object, yet never have to worry about a resource leak; the <CODE>map</CODE> to which <CODE>collisionMap</CODE> points will be automatically destroyed when <CODE>collisionMap</CODE> is. <NOBR>Thus:<SCRIPT>create_link(56);</SCRIPT>
</NOBR></P><A NAME="54463"></A>
<UL><PRE>class SpaceShip: public GameObject {
private:
  static HitMap * initializeCollisionMap();
<A NAME="7620"></A>
  ...
<A NAME="7623"></A>
};
<A NAME="35446"></A>
SpaceShip::HitFunctionPtr
SpaceShip::lookup(const GameObject&amp; whatWeHit)
{
  static auto_ptr&lt;HitMap&gt;
    collisionMap(initializeCollisionMap());
  ...
<A NAME="35448"></A>
}
</PRE>
</UL>

<P><A NAME="dingp57"></A><A NAME="35020"></A>
The clearest way to implement <CODE>initializeCollisionMap</CODE> would seem to be <NOBR>this,<SCRIPT>create_link(57);</SCRIPT>
</NOBR></P><A NAME="54477"></A>

<UL><PRE>SpaceShip::HitMap * SpaceShip::initializeCollisionMap()
{
  HitMap *phm = new HitMap;
<A NAME="54476"></A>
  (*phm)["SpaceShip"] = &amp;hitSpaceShip;
  (*phm)["SpaceStation"] = &amp;hitSpaceStation;
  (*phm)["Asteroid"] = &amp;hitAsteroid;
<A NAME="54486"></A>
  return phm;
}
</PRE>
</UL>

<P><A NAME="dingp58"></A><A NAME="35693"></A>
but as I noted earlier, this won't compile. That's because a <CODE>HitMap</CODE> is declared to hold pointers to member functions that all take the same <A NAME="p241"></A>type of argument, namely <CODE>GameObject</CODE>. But <CODE>hitSpaceShip</CODE> takes a <CODE>SpaceShip</CODE>, <CODE>hitSpaceStation</CODE> takes a <CODE>SpaceStation</CODE>, and, <CODE>hitAsteroid</CODE> takes an <CODE>Asteroid</CODE>. Even though <CODE>SpaceShip</CODE>, <CODE>SpaceStation</CODE>, and <CODE>Asteroid</CODE> can all be implicitly converted to <CODE>GameObject</CODE>, there is no such conversion for pointers to functions taking these argument <NOBR>types.<SCRIPT>create_link(58);</SCRIPT>
</NOBR></P>

<P><A NAME="dingp59"></A><A NAME="53384"></A>
To placate your compilers, you might be tempted to employ <CODE>reinterpret_cast</CODE>s (see <a href="./MI2_FR.HTM#77216" TARGET="_top">Item 2</A>), which are generally the casts of choice when converting between function pointer <NOBR>types:<SCRIPT>create_link(59);</SCRIPT>
</NOBR></P><A NAME="54490"></A>

<UL><PRE>// A bad idea...
SpaceShip::HitMap * SpaceShip::initializeCollisionMap()
{
  HitMap *phm = new HitMap;
<A NAME="54502"></A>

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -