|
Программирование >> Оптимизация возвращаемого значения
влиял только динамический тип объекта object2, то это же самое можно было бы сделать и для него. Однако все, что происходит во время столкновения, зависит от динамических типов обоих объектов. Как видите, при этом недостаточно вызова функции, которая является виртуальной только для одного объекта. Вам необходима функция, поведение которой было бы виртуальным для более чем одного типа объекта. В языке С++ не существует таких функций. Но вам все равно придется реализовать требуемое поведение. Таким образом, надо найти способ, как это сделать. Один из вариантов заключается в том, чтобы вместо С++ выбрать какой-либо другой язык программирования. Вы можете, например, обратиться к языку CLOS (сокращение от Common Lisp Object System - общая система объектов Lisp). Язык CLOS поддерживает наиболее общий объектно-ориентированный механизм вызова функций - мульти-методы (multi-methods). Мульти-метод - это функция, которая является виртуальной по отношению к произвольному числу параметров. Более того, язык CLOS позволяет управлять разрешением вызовов перегруженных мульти-методов. Но предположим, вы должны написать код игры именно на языке С++, то есть найти собственный способ реализовать двойную диспетчеризацию (double-dispatching). (Этот термин возник в сообществе объектно-ориентированных программистов, которые то, что программисты С++ знают под именем виртуальной функции, называют диспетчером сообщений. Вызов функции, виртуальной по отношению к двум параметрам, называется двойной диспетчеризацией. А функция, виртуальная по отношению к нескольким параметрам, - это, соответственно, множественная диспетчеризация.) Существует несколько подходов к решению поставленной задачи. Все они не лишены недостатков, но это не должно вас удивлять. Язык С++ не обеспечивает двойной диспетчеризации, поэтому вам придется самостоятельно выполнить ту работу, которую обычно делают за вас компиляторы, реализуя виртуальные функции (см. правило 24). Использование виртуальных функций и RTTI Виртуальные функции обеспечивают одиночную диспетчеризацию, а это уже половина того, что требуется для решения задачи. Кроме того, и виртуальные функции создаются компиляторами, поэтому начнем с объявления в классе GameObject виртуальной функции collide. В производных классах эта функция как обычно перегружается: class GameObject { public: virtual void collide(GameObject& otherObject) = 0; class Spaceship: public GameObject { public: virtual void collide(GameObject& otherObject); Здесь показан только производный класс Spaceship, поскольку классы SpaceStation и Asteroid устроены таким же образом. Самый простой способ двойной диспетчеризации - эмулировать виртуальные функции при помощи цепочек if-then-else. Вначале надо определить настоящий тип объекта otherObj ect, а затем перебрать все возможные варианты: При столкновении с объектом неизвестного типа генерируется исключение типа CollisionWithUnknownObject: class CollisionWithUnknownObject { public: CollisionWithUnknownObject (GameObjectSc whatWeHit) ; void Spaceship::collide(GameObjectSc otherObject) { const type infoSc objectType = typeid(otherObject); if (objectType == typeid(Spaceship)) { SpaceShipSc ss = static cast<SpaceShipSc>(otherObject) ; обработка столкновения Spaceship-Spaceship; else if (objectType == typeid(SpaceStation)) { SpaceStationSc ss = static cast<SpaceStationSc>(OtherObject); обработка столкновения SpaceShip-SpaceStation; else if (objectType == typeid(Asteroid)) { AsteroidSc a = static cast<AsteroidSc> (otherObject) ; обработка столкновения SpaceShip-Asteroid; else { throw CollisionWithUnknownObject(otherObject); Обратите внимание, что понадобилось определить тип только одного из сталкивающихся объектов. Второй объект - это *this, и его тип обусловлен механизмом виртуальных функций. Так как мы находимся внутри функции - члена класса Spaceship, то объект *this должен иметь тип Spaceship. Поэтому необходимо определить настоящий тип только объекта otherObject. В этом коде нет ничего сложного. Его легко написать и даже заставить работать. Но хотя код выглядит совершенно безобидно, могут возникнуть проблемы с RTTI (сокращение от Run Time Type Information - информация о типах в процессе исполнения - механизм, позволяющий определять тип объекта во время выполнения программы). Намек на реальную опасность скрыт в последнем операторе else и генерируемом там исключении. Инкапсуляция здесь не пригодится, поскольку каждая функция col 1 ide должна знать обо всех своих родственных классах, то есть классах, наследующих от класса GameObj ect. В частности, если в игру вводится новый тип объектов -и добавляется новый класс - придется обновить каждую RTTI цепочку i f-then-el se программе, в которой может встречаться новый тип объекта. Если вы забудете хотя бы про одну из них, в программе появится неочевидная ошибка. И компиляторы не помогут вам ее обнаружить, поскольку не знают о том, что вы делаете. Такой подход к работе с типами данных имеет давнюю историю в языке С и обычно приводит к созданию программ, которые, в сущности, невозможно поддерживать. Ул5шение подобных программ практически немыслимо. Это одна из основных причин, из-за которых были изобретены виртуальные функции: чтобы перенести тяжесть создания и поддержки типизированных вызовов функций с плеч программистов на компиляторы. Но когда для реализации двойной диспетчеризации используется RTTI, вы снова возвращаетесь в старые недобрые времена. Прежние методы приводили к ошибкам в С, и они также станут причиной проблем в С++. Поэтому в функцию collide включен заключительный оператор else, который перехватывает управление при столкновении с неизвестным объектом. Такая ситуация, в принципе, невозможна, но где были ваши принципы, когда вы решили использовать RTTI? Существуют различные способы обработки непредвиденных столкновений, но ни один из них не является удовлетворительным. В рассмотренном сл5ае сгенерировано исключение, но неясно, будет ли оно лучше обработано в вызвавшей его программе, чем при помощи написанного вами кода, поскольку игровой объект столкнулся с чем-то таким, о существовании чего вы даже не догадывались. Использование только виртуальных функций Существует способ снизить вероятность ошибок, присущих реализации двойной диспетчеризации при помощи RTTI, но перед тем как перейти к нему, рассмотрим, как можно решить данную задачу, используя только виртуальные функции. Эта стратегия начинается с той же основной структуры, что и подход RTTI. Функция collide в классе GameObject объявляется виртуальной и переопределяется в каждом из производных классов. Кроме того, функция collide перегружается в каждом из классов, по одной перегрузке для каждого из производных классов в иерархии: class Spaceship; Предварительное объявление, class SpaceStation; class Asteroid; class GameObject { public: virtual void collide(GameObjectSc otherObject) = 0; virtual void collide(SpaceShipSc otherObject) = 0; virtual void collide(SpaceStation& otherObject) = 0; virtual void collide(AsteroidSc otherobject) = 0; class Spaceship: public GameObject {
|
© 2006 - 2024 pmbk.ru. Генерация страницы: 0
При копировании материалов приветствуются ссылки. |