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

📄 mi31.htm

📁 一个非常适合初学者入门的有关c++的文档
💻 HTM
📖 第 1 页 / 共 4 页
字号:
    (*phm)[makeStringPair("SpaceShip","Asteroid")] =      &shipAsteroid;    (*phm)[makeStringPair("SpaceShip", "SpaceStation")] =      &shipStation;  ...  return phm;}} // end namespacelookup must also be modified to work with the pair<string, string> objects that now comprise the first component of the collision map: namespace {          // I explain this below  trust me  HitFunctionPtr lookup(const string& class1,                        const string& class2)  {    static auto_ptr<HitMap>      collisionMap(initializeCollisionMap());    // see below for a description of make_pair    HitMap::iterator mapEntry=      collisionMap->find(make_pair(class1, class2));    if (mapEntry == collisionMap->end()) return 0;    return (*mapEntry).second;  }} // end namespaceThis is almost exactly what we had before. The only real difference is the use of the make_pair function in this statement: HitMap::iterator mapEntry=  collisionMap->find(make_pair(class1, class2));make_pair is just a convenience function (template) in the standard library (see Item E49 and Item 35) that saves us the trouble of specifying the types when constructing a pair object. We could just as well have written the statement like this: HitMap::iterator mapEntry=  collisionMap->find(pair<string,string>(class1, class2));This calls for more typing, however, and specifying the types for the pair is redundant (they're the same as the types of class1 and class2), so the make_pair form is more commonly used.Because makeStringPair, initializeCollisionMap, and lookup 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 declarations.We have finally achieved our goals. If new subclasses of GameObject 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 switch or if-then-else conditionals to maintain. The addition of new classes to the hierarchy requires only well-defined and localized changes to our system: the addition of one or more map insertions in initializeCollisionMap and the declarations of the new collision-processing functions in the unnamed namespace associated with the implementation of processCollision. It may have been a lot of work to get here, but at least the trip was worthwhile. Yes? Yes?Maybe.Inheritance and Emulated Virtual Function TablesThere is one final problem we must confront. (If, at this point, you are wondering if there will always 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 Item 33 and made the concrete classes CommercialShip and MilitaryShip inherit from the newly abstract class SpaceShip: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 CommercialShip and MilitaryShip were added. In particular, if a MilitaryShip object and an Asteroid collided, we'd expect void shipAsteroid(GameObject& spaceShip,                  GameObject& asteroid);to be called. It would not be. Instead, an UnknownCollision exception would be thrown. That's because lookup would be asked to find a function corresponding to the type names "MilitaryShip" and "Asteroid," and no such function would be found in collisionMap. Even though a MilitaryShip can be treated like a SpaceShip, lookup has no way of knowing that.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 sometimes.Initializing Emulated Virtual Function Tables (Reprise)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 collisionMap.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 it.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 example: class CollisionMap {public:  typedef void (*HitFunctionPtr)(GameObject&, GameObject&);  void addEntry(const string& type1,                const string& type2,                HitFunctionPtr collisionFunction,                bool symmetric = true);               // see below  void removeEntry(const string& type1,                   const string& type2);  HitFunctionPtr lookup(const string& type1,                        const string& type2);  // this function returns a reference to the one and only  // map  see Item 26  static CollisionMap& theCollisionMap();private:  // these functions are private to prevent the creation  // of multiple maps  see Item 26  CollisionMap();  CollisionMap(const CollisionMap&);};This class lets us add entries to the map, remove them from it, and look up the collision-processing function associated with a particular pair of type names. It also uses the techniques of Item 26 to limit the number of CollisionMap objects to one, because there is only one map in our system. (More complex games with multiple maps are easy to imagine.) Finally, it allows us to simplify the addition of symmetric collisions to the map (i.e., collisions in which the effect of an object of type T1 hitting an object of type T2 are the same as that of an object of type T2 hitting an object of type T1) by automatically adding the implied map entry when addEntry is called with the optional parameter symmetric set to true.With the CollisionMap class, each client wishing to add an entry to the map does so directly: void shipAsteroid(GameObject& spaceShip,                  GameObject& asteroid);CollisionMap::theCollisionMap().addEntry("SpaceShip",                                         "Asteroid",                                         &shipAsteroid);void shipStation(GameObject& spaceShip,                 GameObject& spaceStation);CollisionMap::theCollisionMap().addEntry("SpaceShip",                                         "SpaceStation",                                         &shipStation);void asteroidStation(GameObject& asteroid,                     GameObject& spaceStation);CollisionMap::theCollisionMap().addEntry("Asteroid",                                         "SpaceStation",                                         &asteroidStation);...Care must be taken to ensure that these map entries are added to the map before any collisions occur that would call the associated functions. One way to do this would be to have constructors in GameObject subclasses check to make sure the appropriate mappings had been added each time an object was created. Such an approach would exact a small performance penalty at runtime. An alternative would be to create a RegisterCollisionFunction class: class RegisterCollisionFunction {public:  RegisterCollisionFunction(          const string& type1,          const string& type2,          CollisionMap::HitFunctionPtr collisionFunction,          bool symmetric = true)  {    CollisionMap::theCollisionMap().addEntry(type1, type2,                                             collisionFunction,                                             symmetric);  }};Clients could then use global objects of this type to automatically register the functions they need: RegisterCollisionFunction cf1("SpaceShip", "Asteroid",                              &shipAsteroid);RegisterCollisionFunction cf2("SpaceShip", "SpaceStation",                              &shipStation);RegisterCollisionFunction cf3("Asteroid", "SpaceStation",                              &asteroidStation);...int main(int argc, char * argv[]){  ...}Because these objects are created before main is invoked, the functions their constructors register are also added to the map before main is called. If, later, a new derived class is added class Satellite: public GameObject { ... };and one or more new collision-processing functions are written, void satelliteShip(GameObject& satellite,                   GameObject& spaceShip);void satelliteAsteroid(GameObject& satellite,                       GameObject& asteroid);these new functions can be similarly added to the map without disturbing existing code: RegisterCollisionFunction cf4("Satellite", "SpaceShip",                              &satelliteShip);RegisterCollisionFunction cf5("Satellite", "Asteroid",                              &satelliteAsteroid);This doesn't change the fact that there's no perfect way to implement multiple dispatch, but it does make it easy to provide data for a map-based implementation if we decide such an approach is the best match for our needs. Back to Item 30: Proxy classesContinue to Miscellany 11 It turns out that it's not so predictable after all. The C++ standard doesn't specify the return value of type_info::name, and different implementations behave differently. (Given a class SpaceShip, for example, one implementation's type_info::name returns "class SpaceShip".) A better design would identify a class by the address of its associated type_info object, because that is guaranteed to be unique. HitMap would then be declared to be of type map<const type_info*, HitFunctionPtr>.Return 

⌨️ 快捷键说明

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