А. Александреску - Современное проектирование на C++ (1119444), страница 48
Текст из файла (страница 48)
Компоненты ( гесигп ро1птее == гпа. роз атее ; // диалогично для оператора != Новый оператор является членом класса, предназначенным исключительно для сравнения объектов классов 5вагтятг<... >, Прелесть этого ликвидатора неоднозначностей заключается в том, что он сравнивает интеллектуальные указатели, как обычные указатели. Если сравнить два интеллектуальных указателя на объекты классов дрр1е и пгапде, код булет, по сушеству, эквивалентным сравнению обычных указателей на классы лрр1е и Огапде. Если это сравнение имеет смысл, код компилируется, в противном случае выдается сообшение об ошибке.
виаггвтг<дрр1е> зр1„ Вмагтгтг<сгапде> зр2; зУ (зр1 = зр2) // семантически эквивалентно сравнению // ар1.робптее == эр2.розпсее Остался один неприятный артефакт — непосредственная проверка 1Ф (зр). Вот это уже действительно интересно! Оператор 1Е применяется только к выражениям арифметического типа или к указателям. Следовательно, чтобы скомпилировать оператор 1Ф (эр), нужно определить автоматическое преобразование интеллектуального указателя в арифметический тип или в обычный указатель. Преобразовывать интеллектуальный указатель в арифметический тип не рекомендуется по тем же причинам, которые были указаны при преобразовании в булевский тип. Указатель — это не арифметический тип, и точка! Преобразование интеллектуального указателя в обычный имеет больше смысла, но туг возникают новые прсблемы. Если нужно преобразовать интеллектуальный указатель в тип, на который он ссылается (см.
предыдуший раздел), у нас есть выбор: либо рисковать, лопуская возможность непредвиденного вызова оператора де1есе, или воздержаться от проверки 1Е (ар). Что выбрать: неудобство или опасность? Следует предпочесть безопасность, поэтому проверку зб (зр) выполнять не следует. Вместо этого можно выбрать проверку з Е(зр ! = О) или более вычурное выражение 1Ф(!! зр). Если вы не хотите предусматривать преобразование интеллектуального указателя в тип, на который он ссылается, можно прибегнуть к интересному трюку, который позволит все же выполнить проверку 1Е (зр). Внутри шаблонного класса вваггргг нужно определить внутренний класс тезсег и преобразование в тип тезтег*, как показано ниже. темр1ате <с1азз т> с1ааа ввагсятг ( с1аьа тезтег ( брозд орегасог де1ете(нозд*); риб11с: орегатог теэтег*0 сопэт ( з Ф (!розпгее ) гетега 0; атагзс тезтег тезт; гетигп Мезе; Глава 7.
Интеллектуальные указатели Теперь при компиляции оператора эт (зр) в действие вступит оператор тезтег", Этот оператор возвращает нулевое значение тогда и только тогда, когда переменная розптее равна нулю. Класс тезтег блокирует оператор г(е1ете, поэтому, если ка- кой-нибудь код вызовет оператор де1ете зр, возникнет ошибка компиляции. Обрати- те внимание на то, что определение самого класса тезсег находится в закрытом раз- деле класса 5иагсятг, поэтому клиентский код ничего не знает о нем.
Класс 5вагтртг решает проблемы, связанные с проверкой равенства и неравенст- ва, следующим образом. ° Определяются операторы == и ! двух видов (шаблонные или нешаблонные). ° Определяется оператор ! . ° Если допускается автоматическое преобразование интеллектуального указателя в тип, на который он ссылается, то определяется дополнительное преобразование в тип чотд>, преднамеренно создающее неоднозначность при вызове оператора де)ете.
В противном случае определяется внутренний класс тезтеГ, Объявляющий закрытый оператор де1есе и определяющий преобразование класса 5иагтртг в тип тезтег", возвращающее нулевой указатель тогда и только тогда, когда объект, на который ссылается интеллектуальный указатель, является нулевым, 7.9. Отношения порядка К операторам отношения порядка относятся операторы «, ьч > и >ьъ Все эти операторы можно свес~и к одному оператору <, Допускать ли упорядоченносп интеллектуальных указателей — вопрос интересный сам по себе. Он основан на двойственной природе указателей, которая часто смущает программистов. Указатели одновременно являются итераторами и моникерами (шопйеь). Будучи итераторами, указатели могут перемешаться по массиву объектов.
Арифметика указателей, включая операции сравнения, поддерживает их итеративную природу. В то же время указатели являются моникерами — удобным способом быстрого доступа к объектам. Эту ипостась указателей обеспечивают операторы разыменования и ->. Двойственная природа указателей иногда может приводить в замешательство, осо- Ъ бенно когда указатель нужно испольэовать только в одном качестве.
Для работы с векторами может понадобиться как перемещение по массиву, так и разыменование, в то время как перемещение по связанному списку или манипуляция индивидуальными объектами используют только операцию разыменования. Отношения порядка между указателями определены только лдя указателей, принадлежащих непрерывному участку памяти. Иными словами, сравнивать между собой можно лишь те указатели.
которые ссылаются на элементы одного и того же массива. Определение отношений порядка для интеллектуальных указателей порождает вопроси могут ли интеллектуальные указатели ссылаться на обьекты. хранящиеся в одном и том же массиве? На первый взгляд нет. Основное свойство интеллектуальных указателей заключается в том, что они управляют правами владения объектами, а объекты с разными правами владения не могут принадлежать одному и тому же массиву.
Следовательно, было бы рискованно выполнять бессмысленные сравнения. Если отношения порядка действительно необходимы, всегда можно применить явный доступ к обычному указателю, хранящемуся внутри интеллектуального. Здесь снова нужно искать наиболее безопасный и выразительный способ сравнения. В предыдущем разделе мы пришли к выводу, что неявное преобразование интеллектуального указателя в обычный — вопрос выбора. Если пользователь класса Часть И.
Компоненты гоо 5вагсрсг позволяет неявное преобразование, то приведенный ниже код будет успешно скомпилирован. 5вагсрсг<5овеснзпд> зр1, зр2; )б (зр1 < зр2) // Превращаем интеллектуальные указатели // зр1 и зр2 в обычные, а затем сравниваем их Это значит, что блокировать отношения порядка следует явно. Для зтого достаточно объявить их,но не определять, При попытке выполнить такую проверку во время редактирования связей возникнет ошибка.
севр1асе <с1азз т> с1азз 5вагсясг ( ); севр1асе <с1авв т, с1авв н> Ьоо1 орегасог<(сопзс 5вагсясг<т>й, сопзс мй); // не определен севр1асе <с1авв т, с1авз н> ьоо1 орегасог<(сопзс тй, сопзс 5вагсрсг<ц>й); // не определен Благоразумнее выразить остальные операторы через оператор <, оставив его неопределенным.
Таким образом, если пользователь класса 5вагсрсг все же захочет установить между его объектами отношение порядка, ему останется лишь определить соответствуюший оператор <. // ликвидатор неоднозначностей Севр1ате <с1ааз т, с1азз ц> Ьоо! орегасог<(сопзс 5вагсрсг<т>й 1Ьз, сопзс 5вагсрсг<н>й гнз) ( гесмгп 1бв < бествр1(гбв) // все остальные операторы севр1асе <с1азз т, с1ава н> Ьоо1 орегасог>(5вагСРСг<т>й 1Ьз, сопзс пй гбз) ( гесмгп гнз < 1Ьз; аналогично для остальных операторов Обратите внимание на ликвидатор неоднозначностей. Теперь, если какой-нибудь пользователь библиотеки полагает, что объекты класса 5вагсрсг<ы!ддес> должны быть упорядочены, он может написать следуюший код.
)п1тпе Ьоо! орегасог<(сопев 5вагСРСг<ы)бдес>й 1Ьз, сопзс ы!одет" гбз) ( гесмгп сессвр1(1бв) < гнз; з'п!тпе Ьоо! орегасог (сопзс Мендес* 1Ьз, сопев 5вагсрсг<и!бдес>й гбв) гесмгп 1бз < аествр1(гнз); ) Жаль, что пользователь должен определять два оператора, а не олин, но зто все же лучше, чем если бы он определял все восемь операторов. На атом обсуждение отношения порядка между интеллектуальными указателями можно было бы закончить. Ничего интересного в втой проблеме обнаружить не удатлось. Иногда очень полезно установить отношение порядка между произвольно раз- 201 Глава 7. Интеллектуальныеуквзатели мешенными в памяти объектами, а не только между объектами, хранящимися в одном и том же массиве.
Например, иногда нужно хранить вспомогательную информацию о каждом объекте и быстро ее извлекать. Упорядоченный ассоциативный массив (шар) адресов этих объектов — очень эффективное решение этой проблемы. Станларт языка С++ позволяет легко воплощать такие замыслы. Хотя сравнение указателей на произвольно размещенные объекты не определено, стандарт гарантирует, что оператор этд::1еав даст осмысленные результаты, если применить его к двум указателям, имеющим одинаковый тип. Поскольку стандартные ассоциативные контейнеры используют оператор этб::1еээ в качестве отношения порядка, заданного по умолчанию, можно вполне безопасно пользоваться ассоциативными массивами, ключами которых являются указатели.
Класс 5вагтгтг также поддерживает эту идиому. Следовательно, класс 5иагтРтг осуществляет специализацию оператора этб::1езэ. Эта специализация заключается в простой переадресации вызова оператору этб::1еэа для обычных указателей. павезрасе этб ( тевр1ате <с1авв т> август 1еаа<5вагтятг<т» рцЬ)зс Ьз'вагу твист(оп<5вагтРтг<т>, 5ваггРтг<т>, Ьоо1> ( Ьоо1 орегатогС) совах 5иагтгтг<т>5 1Ь5, СОПат 5вагтРтг<т>4 гйа) сопэт гетцгп 1еаа<т*>О (пехтвр1(1Ьа), беттвр1(гйэ)); ) ): ) Итак, класс 5вагтгтг не определяет операторы упорядочения по умолчанию. В нем объявляются (но не реализуются) два обобщенных оператора <, а остальные операторы выражаются через них.
Пользователь может использовать либо специализированную, либо обобщенную версии оператора <. Класс 5магтятг осуществляет специализацию оператора аъч)::1еаа, устанавливая отношение порядка между произвольными интеллектуальными указателями. 7.10. Обнаружение и регистрация ошибок В разных приложениях требуется разная степень безопасности, обеспечиваемая интеллектуальными указателями. Некоторые программы выполняют интенсивные вычисления, поэтому нужно оптимизировать их быстродействие, другие (фактически большинство) интенсивно осуществляют операции ввода-вывода информации и нуждаются в эффективных средствах проверки данных, не снижающих их производительность. Чаше всего в приложении нужны обе модели; низкая безопасность, высокая скорость в некоторых критических местах и высокая безопасность, низкая скорость — в остальных частях программы.
Вопросы, связанные с проверкой интеллектуальных указателей, можно разделить на две категории: инициализация проверки и проверка перед разыменованием. 7. у О. у. Проверка во время инициализации Может ли интеллектуальный указатель принимать нулевое значение? 202 Часть И. Компоненты Легко можно сделать так, что интеллектуальный указатель никогда не сможет стать нулевым (очень полезное свойство в практических приложениях). Это значит, что любой интеллектуальный указатель всегда корректен (если вы не манипулируете простыми указателями с помощью функции аеттвр1яет). Реализовать это свойство интеллектуального указателя можно с помощью конструктора, генерирующего исключительную ситуацию при получении нулевого указателя.