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

📄 mi31.htm

📁 一个非常适合初学者入门的有关c++的文档
💻 HTM
📖 第 1 页 / 共 5 页
字号:
  (*phm)["SpaceShip"] =
    reinterpret_cast<HitFunctionPtr>(&hitSpaceShip);
<A NAME="54503"></A>
  (*phm)["SpaceStation"] =
    reinterpret_cast&lt;HitFunctionPtr&gt;(&amp;hitSpaceStation);
<A NAME="54504"></A>
  (*phm)["Asteroid"] =
    reinterpret_cast&lt;HitFunctionPtr&gt;(&amp;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 &#151; 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&amp; otherObject) = 0;
  ...
};
<A NAME="53620"></A>
<A NAME="p243"></A>class SpaceShip: public GameObject {
public:
  virtual void collide(GameObject&amp; otherObject);
<A NAME="53630"></A>
  // these functions now all take a GameObject parameter
  virtual void hitSpaceShip(GameObject&amp; spaceShip);
  virtual void hitSpaceStation(GameObject&amp; spaceStation);
  virtual void hitAsteroid(GameObject&amp; 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 &#151; 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"] = &amp;hitSpaceShip;
  (*phm)["SpaceStation"] = &amp;hitSpaceStation;
  (*phm)["Asteroid"] = &amp;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&amp; spaceShip)
{
  SpaceShip&amp; otherShip=
    dynamic_cast&lt;SpaceShip&amp;&gt;(spaceShip);
<A NAME="53688"></A>
  <I>process a SpaceShip-SpaceShip collision;</I>
<A NAME="53689"></A>
}
<A NAME="53691"></A>
void SpaceShip::hitSpaceStation(GameObject&amp; spaceStation)
{
  SpaceStation&amp; station=
    dynamic_cast&lt;SpaceStation&amp;&gt;(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&amp; asteroid)
{
  Asteroid&amp; theAsteroid =
    dynamic_cast&lt;Asteroid&amp;&gt;(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 &#151; see below
<A NAME="36161"></A>
  // primary collision-processing functions
  void shipAsteroid(GameObject&amp; spaceShip,
                    GameObject&amp; asteroid);
<A NAME="53847"></A>
  void shipStation(GameObject&amp; spaceShip,
                   GameObject&amp; spaceStation);
<A NAME="53848"></A>
  void asteroidStation(GameObject&amp; asteroid,
                       GameObject&amp; spaceStation);
  ...
<A NAME="36165"></A>
  // secondary collision-processing functions that just
  // implement symmetry: swap the parameters and call a
  // primary function
  void asteroidShip(GameObject&amp; asteroid,
                    GameObject&amp; spaceShip)
  { shipAsteroid(spaceShip, asteroid); }
<A NAME="35990"></A>
  void stationShip(GameObject&amp; spaceStation,
                   GameObject&amp; spaceShip)
  { shipStation(spaceShip, spaceStation); }
<A NAME="35991"></A>
  void stationAsteroid(GameObject&amp; spaceStation,
                       GameObject&amp; 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&amp;, GameObject&amp;);
  typedef map&lt; pair&lt;string,string&gt;, HitFunctionPtr &gt; HitMap;
<A NAME="54566"></A>
  pair&lt;string,string&gt; makeStringPair(const char *s1,
                                     const char *s2);
<A NAME="54615"></A>
  HitMap * initializeCollisionMap();
<A NAME="36013"></A>
  HitFunctionPtr lookup(const string&amp; class1,
                        const string&amp; class2);
<A NAME="35998"></A>

⌨️ 快捷键说明

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