|
Программирование >> Оптимизация возвращаемого значения
Обратите внимание на неименованное пространство имен, содержащее функции для реализации processCollision. Все, находящееся в таком неименованном пространстве имен, будет закрытым внутри текущего модуля трансляции (по сути, в текущем файле) - это то же самое, если бы функции были объявлены как static для файла. Но с введением пространств имен такое объявление устарело, и вам следует привыкать использовать неименованные пространства имен, если ваши компиляторы поддерживают их. Концептуально такая реализация очень похожа на реализацию с использованием функций - членов класса, за исключением небольших различий. Во-первых, HitFunctionPtr теперь является указателем на функцию, не являющуюся членом класса. Во-вторых, класс исключений CollisionWithUnknownObject переименован в UnknownCollision и имеет в качестве аргументов два объекта, а не один. И наконец, функция lookup обладает двумя параметрами и выполняет обе части двойной диспетчеризации. Это означает, что карта столкновений должна теперь содержать три блока данных: два имени типов и указатель HitFunctionPtr. Но класс тар способен включать только два блока данных. Обойти возникшую проблему можно с помощью стандартного шаблона pair, который позволяет упаковать два имени типа в один объект initializeCollisionMap. Вместе со вспомогательной функцией makeStringPair этот объект будет выглядеть так: / / Эта функция создает пару объектов pair<string,string> из двух строк char*. Эти пары используются позже в функции initializeCollisionMap. Обратите внимание,что данная функция позволяет выполнять оптимизацию возвращаемого значения (см. правило 20). namespace { Неименованное пространство имен - см. ниже. pair<string,string> makeStringPair(const char *sl, const char *s2) { return pair<string,string>(si, s2); } } Конец пространства имен. namespace { Еще одно неименованное пространство имен. HitMap * initializeCollisionMapО { HitMap *phm = new HitMap; (*phm)[makeStringPair( Spaceship , Asteroid )] = ScshipAsteroid; (*phm)[makeStringPair( Spaceship , SpaceStation )] = StshipStation; return phm; } Конец пространства имен. Нужно изменить также функцию lookup, чтобы она работала с объектами pair<string, string>, которые теперь включают первый компонент карты столкновений: namespace { Это будет объяснено ниже. HitFunctionPtr lookup(const stringSc classl, const stringSc class2) static auto ptr<HitMap> collisionMap(initializeCollisionMap()); Cm. ниже описание функции make pair. HitMap::iterator mapEntry= collisionMap->find(make pair (classl, class2)); if (mapEntry == collisionMap->end()) return 0; return {*mapEntry).second; , } Конец пространства имен. Это почти то же, чем вы располагали раньше. Единственное различие состоит в использовании функции make pair в операторе: HitMap::iterator mapEntry= collisionMap->find(make pair(classl, class2)); Функция (шаблон) make pai г введена в стандартную библиотеку только для удобства (см. правило 35) и избавляет вас от хлопот по заданию типов при создании объекта pair. С тем же успехом можно было бы записать этот оператор следующим образом: HitMap::iterator mapEntry= collisionMap->find(pair<string,string>(classl, class2)); Ho такая запись длиннее, и задание типов для pair является избыточным (они совпадают с типами для classl и class2), поэтому чаще используется форма с функцией make pair. Поскольку функции makeStringPair, initializeCollisionMap и lookup были объявлены в неименованном пространстве имен, то каждая из них должна быть реализована именно там. Вот почему эти функции реализованы в неименованном пространстве имен (в том же модуле трансляции, где находятся их объявления): компоновщик корректно свяжет их определения (то есть реализацию) с ранее сделанными объявлениями. Итак, цель достигнута. Если в иерархию добавляются новые под классы класса GameObj ect, то существующие классы не будут нуждаться в перекомпиляции (если они не собираются использовать новые классы). Вы избавились от неразберихи, которая возникает при использовании переключателя switch, основного на RTTI, и поддержке условных операторов if-then-else. Добавление к иерархии новых классов требует только хорошо определенных и локализованных изменений в системе: одной или двух вставок в функции initializeCollisionMap и объявления новых функций для обработки столкновений в неименованном пространстве имен, связанном с реализацией функции processCollision. Чтобы дойти до этого места, потребовалось много усилий, но, согласитесь, путешествие того стоило. Наследование и эмулированные таблицы виртуальных функций Осталось устранить последнюю проблему. (Впрочем, механизм реализации виртуальных функций настолько сложен, что практически всегда за последней проблемой будет всплывать еще какая-нибудь.) Все что вы сделали, будет прекрасно действовать до тех, пор пока вам не понадобится разрешить при вызове функций для обработки столкновений преобразование типов, основанное на наследовании. Но предположим, что в создаваемой игре иногда требуется различать военные и гражданские космические корабли. Можно было бы модифицировать иерархию, руководствуясь правилом 33 и сделав реальные классы Commercial Ship (Гражданский корабль) и MilitaryShip (Военный корабль) наследниками нового абстрактного класса Spaceship (см. рис. 5.20). (SpacoShip (acoStau (steroid / Cornmetci.-il Ч V. Ship . Military >v Ship J Допустим, что гражданские и военные корабли при столкновении с чем-либо ведут себя одинаково. Следовательно, они смогут использовать те же функции обработки столкновений, которые применялись до добавления классов CommercialShip и MilitaryShip. В таком случае при столкновении, например, объектов MilitaryShip и Asteroid должна вызываться функция: void shipAsteroid(GameObjectSc spaceship, GameObjectSc asteroid) ; Ho это не так. В действительности будет сгенерировано исключкние Unknown-Col 1 i s ion. Это произойдет потому, что функция lookup должна будет найти функции, соответствующие типам с именами MilitaryShip и Asteroid, а в массиве collisionMap нет таких функций. Даже если с объектом MilitaryShip разрешалось бы обращаться как с объектом Spaceship, функция lookup не может ничего знать об этом. Если вам нужно реализовать двойную диспетчеризацию, и при этом поддерживать основанное на наследовании преобразование параметров, то единственным
|
© 2006 - 2024 pmbk.ru. Генерация страницы: 0
При копировании материалов приветствуются ссылки. |