|
Программирование >> Оптимизация возвращаемого значения
успешной реализации класса Spaceship, классы SpaceStation и Asteroid можно реализовать аналогично. В функции SpaceShip::collide нужно каким-то образом сопоставить динамическому типу параметра otherObject указатель на соответствующую функцию для обработки столкновений. Простейший способ сделать это заключается в создании ассоциативного массива, который дает указатель на соответствующую функцию - член класса по заданному имени класса. Можно напрямую реализовать функцию collide при помощи такого ассоциативного массива, но Л5ше добавить промежуточную функцию lookup, принимающую объект GameObject и возвращающую соответствующий указатель на функцию. То есть вы передаете функции lookup объект GameObj ect, и она возвращает указатель на функцию - член класса, которая будет вызываться, если вы столкнетесь с чем-то типа GameObject. Функция lookup объявляется так: class Spaceship: public GameObject { private: typedef void (Spaceship::*HitFunctionPtr)(GameObject&); static HitFunctionPtr lookup(const GameObject& whatWeHit); Синтаксис указателей на функции никогда не бывает особенно удобным, а для указателей на функции - члены класса он еще хуже, чем обычно, поэтому директивой typedef HitFunctionPtr был определен тип, который служит сокращением для указателя на функцию - член класса Spaceship, принимающую в качестве параметра GameObj ееt& и ничего не возвращающую. После того как получена функция lookup, реализация функции collide становится легче легкого: void Spaceship::collide(GameObject& otherObject) { HitFunctionPtr hfp = lookup(otherObject); Найти функцию, if (hfp) { Если функция найдена, (this->*hfp)(otherObject); вызвать ее. else { throw CollisionWithUnknownObject(otherObject); Если вы синхронизировали содержимое ассоциативного массива с иерархией классов, производных от GameObject, функция lookup всегда должна находить допустимый указатель на функцию для объекта, который вы ей передаете. Но люди всегда остаются людьми, и ошибки иногда появляются даже в очень аккуратно написанных программных системах. Вот почему надо проверить, возвращается ли функцией lookup корректный указатель, и сгенерировать исключение, если происходит невозможное, в результате чего вызов функции завершается неудачей. Остается теперь написать функцию lookup. Если имеется ассоциативный массив, который сопоставляет типы объектов указателям на функции - члены соответствующих классов, сам поиск выполняется достаточно легко, но создание, инициализация и уничтожение ассоциативного массива само по себе является непростой задачей. Такой массив должен создаваться и инициализироваться до того, как он будет использоваться, и уничтожаться, когда он больше не нужен. Можно было бы создавать и удалять массив врзную с помощью операторов new и delete, но это иногда приводит к ошибкам: как гарантировать, что массив не был использован прежде, чем вы соберетесь проинициализировать его? Jljme автоматизировать этот процесс при помощи компилятора, объявив ассоциативный массив статическим в функции lookup. На этот раз он будет создаваться и инициализироваться при первом вызове функции lookup и автоматически уничтожаться через какое-то время после выхода из функции main. Кроме того, вы можете использовать в качестве ассоциативного массива шаблон тар из стандартной библиотеки шаблонов (см. правило 35): class Spaceship: public GameObject { private: typedef void (Spaceship::*HitFunctionPtr)(GameObject&); typedef map<string, HitFunctionPtr> HitMap; Spaceship::HitFunctionPtr Spaceship::lookup(const GameObjectSc whatWeHit) { static HitMap collisionMap; Здесь collisionMap и является ассоциативным массивом. Он сопоставляет имени класса (как объекту типа string) указатель на функцию - член класса Spaceship. Так как выговорить map<string, HitFunctionPtr> нелегко, было использовано определение типа. (Можете попытаться в качестве развлечения переписать определение collisionMap, отказавшись от определения типов HitMap и HitFunctionPtr. Немногие отважатся сделать это дважды.) После создания collisionMap реализация функции lookup становится достаточно простой. Это связано с тем, что операция поиска непосредственно поддерживается классом тар, и единственная переносимая функция - член класса, с помощью которой можно всегда проконтролировать результат вызова typeid. Это функция name (она, как и следовало ожидать*, возвращает имя динами- * Оказывается, что это не столь предсказуемо. Стандарт языка С++ не определяет возвращаемое функцией type info: :name значение, и различные реализации ведут себя по-разному. (В одной из реализаций, например, функция type inf о: : name возвращает class Spaceship, если задан класс Spaceship.) Лучше было бы идентифицировать класс по адресу связанного с ним объекта type inf о, так как этот адрес гарантированно является уникальным. Тогда тип HitMap объявлялся бы как map<const type info* ,HitFunctionPtr>. ческого типа объекта). Тогда для реализации функции lookup достаточно найти в массиве collisionMap ячейку, соответствующую динамическому типу аргумента функции lookup. Код для функции lookup довольно прост, но если вы не знакомы со стандартной библиотекой шаблонов (см. правило 35), он может вызвать у вас затруднения. Не беспокойтесь. Комментарии в функции объясняют, что происходит. Spaceship::HitFunctionPtr Spaceship::lookup(const GameObjectSc whatWeHit) { static HitMap collisionMap; Вы увидите инициализацию collisionMap позже. Поиск функции обработки столкновения для типа whatWeHit. Возвращаемое значение - это похожий на указатель объект, который называется итератором (см. правило 35) . HitMap::iterator mapEntry= collisionMap.find(typeid(whatWeHit).name()); mapEntry == collisionMap.end() , если поиск был неудачным; / / это стандартное поведение для тар. Снова см. правило 35. if (mapEntry == collisionMap.end()) return 0; Если вы оказались здесь, поиск был успешным. mapEntry указывает на полную ячейку карты, состоящую из пары (string, HitFunctionPtr) . Вам нужна только вторая часть пары, поэтому она и возвращается. return (*mapEntry).second; Последний оператор в функции возвращает ( *mapEntrY) . second вместо более привычного mapEntry->second, чтобы удовлетворить капризам стандартной библиотеки шаблонов. Подробнее см. на стр. 108. Инициализация эмулированных таблиц виртуальных функций Теперь настала пора инициализировать массив collisionMap. Вы, наверное, хотели бы написать что-то вроде: Неправильная реализация. Spaceship::HitFunctionPtr Spaceship: : lookup (const GameObjectSc whatWeHit) { static HitMap collisionMap; collisionMap[ Spaceship ] = SchitSpaceShip; collisionMap[ SpaceStation ] = SchitSpaceStation; collisionMap[ Asteroid ] = SchitAsteroid;
|
© 2006 - 2024 pmbk.ru. Генерация страницы: 0.001
При копировании материалов приветствуются ссылки. |