|
Программирование >> Оптимизация возвращаемого значения
для того, чтобы не привязывать временные объекты к параметрам, представляющими собой не-const ссылки. И последняя ситуация, когда proxy-объекты не могут незаметно подменять настоящие, возникает при неявном преобразовании типов. Если proxy-объект неявно преобразуется в настоящий объект, который он заменяет, вызывается определенная пользователем функция преобразования. Например, объект CharProxy может быть преобразован в заменяемый им объект типа char при помощи вызова operator char. Как объясняется в правиле 5, компиляторы используют только одну определенную пользователем функцию преобразования при переводе параметра вызова в тип параметра соответствующей функции. В результате может получиться так, что вызовы функций, успешно завершающиеся при передаче в качестве параметров настоящих объектов, будут неудачны при передаче вместо них proxy-объектов. Например, предположим, что имеется класс TVStation (ТВ-станция) и функция watchTV (смотреть ТВ): class TVStation { public: TVStation(int channel); void watchTV(const TVStation& station, float hoursToWatch); Благодаря неявному преобразованию типов из int в TVStation (см. правило 5) можно затем сделать следующее: watchTV(10,2.5); Смотреть 10 канал в течение 2.5 часов. Применив шаблон для массивов с подсчетом ссылок, где вызовы operator [ ] как lvalue и rvalue различаются с помощью proxy-классов, можно было бы записать так: ArraY<int> intArray; intArray[4] =10; watchTV(intArray[4], 2.5); Ошибка! преобразование из Proxy<int> в TVStation не существует. Если не забывать о проблемах, которые сопровождают неявные преобразования типов, то такое поведение не очень шокирует. Хотя лучше было бы объявить конструктор класса TVStation как explicit, в этом случае компилятор не пропустил бы даже первый вызов функции watchTV. Подробнее о неявном преобразовании типов и влиянии на них директивы explicit см. правило 5. Оценка Proxy-классы позволяют добиться поведения, которое сложно или даже невозможно реализовать без них. Самые распространенные примеры - использование многомерных массивов, распознавание lvalue и rvalue и подавление неявных преобразований (см. правило 5). в то же время proxy-классы имеют и недостатки. Будучи возвращаемыми функцией значениями, proxy-объекты являются временными (см. правило 19), поэтому они должны создаваться и уничтожаться. Это приводит к расходам памяти и времени, которые, однако, почти всегда с лихвой компенсируются возможностью отличать операции записи от операций чтения. Само существование proxy-классов увеличивает сложность использующих их программных систем, так как дополнительные классы усложняют, а не облегчают разработку, реализацию, понимание и поддержку. И наконец, переход от класса, который работает с настоящими объектами, к классу, который взаимодейств)чот с proxy-объектами, часто изменяет семантику класса, поскольку proxy-объекты обычно ведут себя немного иначе, чем настоящие объекты, которые они представляют. Иногда из-за этого proxy-объекты оказываются не Л5Д1Ц1им выбором при разработке системы, но во многих классах нет особой необходимости делать присутствие proxy-объектов очевидным для пользователей. Например, вряд ли пользователям понадобится получать адрес объекта ArraylD в примере двумерного массива, приведенного в начале этого раздела, и маловероятно, что объект Arraylndex (см. правило 5) будет передаваться функции, ожидающей другой тип. Во многих случаях proxy-объекты могут вполне приемлемо заменять настоящие объекты. Правило 31. Создавайте функции, виртуальные по отношению более чем к одному объекту Иногда, как говорит Джаклин Сьюзан (Jacqueline Susann), одного раза недостаточно. Предположим, например, что вы пытаетесь занять одну из престижных высокооплачиваемых должностей в известной компании, производящей программное обеспечение, штаб-квартира которой находится в Редмонде, штат Вашингтон (я, конечно же, имею в виду Nintendo). Чтобы привлечь к себе внимание менеджеров компании Nintendo, вы решаете написать видеоигру. Ее действие может происходить в космосе, где находятся разные объекты (Game Obj ect): межпланетные корабли (Space Ship), станции (Space Station) и астероиды (Asteroid). Когда эти объекты проносятся в вашем искусственном мире, они, естественно, периодически сталкиваются друг с другом. Предположим, что законы столкновений таковы: □ если корабль и космическая станция сталкиваются на малой скорости, корабль стыкуется со станцией. В противном случае корабль и станция испытывают повреждения, пропорциональные скорости столкновения; □ если сталкиваются корабль с кораблем или станция со станцией, то оба объекта испытывают повреждения, пропорциональные скорости удара; □ если с кораблем или станцией сталкивается небольшой астероид, то астероид уничтожается. В случае большого астероида уничтожается корабль или станция; □ если сталкиваются два астероида, то оба разбиваются на осколки (маленькие астероиды), которые разлетаются во всех направлениях. Эта игра может показаться довольно cKjHoft, но она приведена здесь не для развлечения, а чтобы рассмотреть структуру кода С++, который обрабатывает столкновения между объектами. Вначале отметим, что астероиды, станции и корабли имеют несколько общих свойств. В частности, это движение, поэтому они все имеют свою скорость перемещения. Будет естественно определить для этих общих свойств базовый класс, от которого все объекты будут наследовать. На практике такой класс почти неизменно будет абстрактным базовым классом (базовые классы вообще всегда должны быть абстрактными - см. правило 33). Следовательно, иерархия может выглядеть примерно так, как представлено на рис. 5.18. (aceStation Рис. 5. 18 class GameObject { ... } ; class Spaceship: public GameObject { ... } ; class SpaceStation: public GameObject { ... class Asteroid: public GameObject { ... } ; Теперь предположим, что вы решили написать код для проверки столкновений объектов. Для этого пригодится, например, такая функция: void checkForCollision(GameObject& objectl, GameObject& object2) if (theyJustCollided(objectl, object2)) { processCollision(objectl, object2); else { И здесь сложность программирования этой задачи становится очевидной. Когда вы вызываете функцию processCollision, вы знаете: objectl и object2 только что столкнулись, и результат столкновения зависит от того, что собой представляют два эти объекта. Но вы не знаете, чем именно они являются, вам известно только то, что оба они имеют тип GameObj ect. Если бы обработка столкновения определялась динамическим типом объекта objectl, то можно было бы сделать функцию processCollision в классе GameObject виртуальной и вызывать функцию objectl .processCollision (object2 ). Если бы на результат
|
© 2006 - 2024 pmbk.ru. Генерация страницы: 0
При копировании материалов приветствуются ссылки. |