|
Программирование >> Оптимизация возвращаемого значения
Но при этом в массив collisionMap будут вставляться указатели на функции - члены класса при каждом вызове функции lookup, что крайне неэффективно. Код также не будет компилироваться, но эта проблема будет рассмотрена чуть позже. Сейчас нужно добиться, чтобы указатели на функции - члены класса помещались в массив collisionMap только один раз, а именно при его создании. Сделать это достаточно легко: просто напишите закрытую статическую функцию -член initializeCollisionMap для создания и инициализации шар, а затем инициализируйте массив collisionMap при помощи возвращаемого функцией initializeCollisionMap значения: class Spaceship: public GameObject { private: static HitMap initializeCollisionMap() , }; Spaceship::HitFunctionPtr Spaceship::lookup(const GameObjectSc whatWeHit) { static HitMap collisionMap = initializeCollisionMap(); Ho это означает, что, возможно, возникнут расходы на копирование возвращаемого функцией initializeCollisionMap объекта map в массив collisionMap (см. правила 19 и 20). Вы наверняка предпочли бы этого не делать. Можно было бы обойтись без таких расходов, если бы функция initializeCollisionMap возвращала указатель, но тогда пришлось бы позаботиться о том, чтобы объект тар, на который ссылался бы данный указатель, уничтожался в соответствующее время. К счастью, существует способ все это сделать. Можно превратить colli s ionMap в интеллектуальный указатель, который автоматически удаляет то, на что он указывает, при уничтожении самого указателя. Стандартная библиотека С++ как раз для таких интеллектуальных указателей содержит шаблон auto ptr (см. правило 9). Если определить collisionMap в функции lookup как статический интеллектуальный указатель типа auto ptr, то функция initializeCollisionMap будет возвращать указатель на инициализированный объект тар, причем без риска утечки ресурсов; объект тар, на который указывает collisionMap, будет автоматически уничтожен одновременно с collisionMap. Таким образом: class Spaceship: public GameObject { private: static HitMap * initializeCollisionMap(); Spaceship::HitFunctionPtr Spaceship::lookup(const GameObjectSc whatWeHit) { static auto ptr<HitMap> collisionMap(initializeCollisionMap ()); Наиболее логичный способ реализации функции initializeCollisionMap, казалось бы, следующий: Spaceship::HitMap * Spaceship::initializeCollisionMap() { HitMap *phm = new HitMap; (*phm)[ Spaceship ] = SchitSpaceShip; (*phm)[ SpaceStation ] = SchitSpaceStation; (*phm) [ Asteroid ] = SchitAsteroid; return phm; Ho, как я уже отмечал, такой код не будет компилироваться. Это связано с тем, что HitMap объявлен как массив указателей на функции - члены класса, имеющие одинаковый тип аргумента, а именно GameObject. Но аргумент функции liitSpaceShip имеет тип SpaceSliip, аргумент функции hitSpaceStation -тип SpaceStation, а аргумент функции liitAsteroid - тип Asteroid. И хотя объекты Spaceship, SpaceStation и Asteroid могут быть неявно преобразованы к типу GameObj ect, для указателей на функции с этими аргументами такого преобразования не существует. У вас может возникнуть искушение использовать операторы приведения типов reinterpret cast (см. правило 2), с помощью которых обычно производят преобразования между типами указателей на функции: Неудачная идея. . . Spaceship::HitMap * Spaceship::initializeCollisionMap() { HitMap *phm = new HitMap; (*phm)[ Spaceship ] = reinterpret cast<HitFunctionPtr>(SchitSpaceShip); (*phm)[ SpaceStation ] = reinterpret cast<HitFunctionPtr>(SchitSpaceStation); (*phm)[ Asteroid ] = reinterpret cast<HitFunctionPtr>(SchitAsteroid); return phm; Этот код будет компилироваться, но сама идея неудачна. Вы собираетесь лгать компилятору, чего никогда нельзя делать. Неправда, что функции hitSpaceShip, hitSpaceStation и hitAsteroid ожидают аргумент GameObject. Функция hitSpaceShip ожидает аргумент типа Spaceship, функция hitSpaceStation -аргумент типа SpaceStation, а функция hitAsteroid-аргумент типа Asteroid. Операторы приведения типов лгут, говоря, что это не так. Компиляторам не нравится, когда им лгут, и они часто находят способ отомстить, если обнаруживают обман. Обычно они генерируют неоптимальный код для функций, вызываемых при помощи *phm, если производные от GameObject классы используют множественное наследование или имеют виртуальные базовые классы. Другими словами, если бы SpaceStation, Spaceship или Asteroid имели другие базовые классы (кроме GameObj ect), то вы могли бы обнаружить, что вызовы функций для обработки столкновений внутри функции collide ведут-себя очень грубо. Элементы данных объекта В Виртуальный указатель Указатель на виртуальный енты данных объекта С Виртуальный указатель Указатель на виртуальный базовый класс Элементы данных объекта D Элементы данных объекта А Виртуальный указатель Рис. 5.19 Рассмотрим снова иерархию наследования A-B-C-D и возможный формат объекта D, описанный в правиле 24 (см. рис. 5.19). Адреса всех четырех частей класса в объекте D различны. Это важно, так как хотя указатели и ссылки ведут себя по-разному (см. правило 1), компиляторы обычно реализуют ссылки, генерируя в создаваемом коде указатели. Таким образом, передача по ссылке обычно осуществляется при помощи передачи указателя на объект. Если по ссылке передается объект, имеющий несколько базовых классов (например, объект D), важно, чтобы компилятор передавал правильный адрес -тот, который соответствует объявленному типу параметра вызываемой функции. Но что, если вы солгали компиляторам и сообщили им, что ваша функция ожидает параметр типа GameObj ect, хотя в действительности она ожидает объект типа Spaceship или SpaceStation? В таком случае они передадут неверный адрес при вызове функции, что, вероятно, приведет к кровавой бойне во время выполнения программы, а причину конфликта будет определить очень сложно. Это одна из веских причин для того, чтобы отказаться от использования операторов приведения типов.
|
© 2006 - 2024 pmbk.ru. Генерация страницы: 0
При копировании материалов приветствуются ссылки. |