С. Мейерс - Эффективный и современный C++ (1114942), страница 12
Текст из файла (страница 12)
Этот объект не имеет имени, но для упрощения нашего рассмотрения я буду называть егоt emp. Для t emp вызывается o p e r a t o r [ ] , в результате чего возвращается объектs t d : : vecto r<boo l > : : re ference, содержащий указатель на слово в структуре данных,хранящей интересующий нас бит (эта структура находится под управлением t emp), плюссмещение в слове, соответствующее пятому биту. Переменная highPriori ty представляет собой копию этого объекта std : : vector<bool > : : reference, так что highPriori t утоже содержит указатель на слово в t emp плюс смещение, соответствующее пятому биту.В конце инструкции объект t emp уничтожается, так как это объект временный.
В результате переменная h i ghPr i o r i t y содержит висячий указатель, что и дает неопределенноеповедение при вызове processWidget:processWidget (w, highPriority) ; / / Неопределенное поведение '/ / highPriority содержит/ / висячий указатель !Класс s t d : : v e c t o r <boo l > : : r e f e r e n c e является примером прокси-класса (proxyclass), т.е. класса, цель которого - эмуляция и дополнение поведения некоторого другого типа. Прокси-классы применяются для множества разных целей. Например,std: : vector<boo l> : : re ference нужен для того, чтобы создать иллюзию, что operator [ Jкласса s t d : : vecto r<bo o l > возвращает ссылку на бит, а интеллектуальные указателистандартной библиотеки (см. главу 4, "Интеллектуальные указатели") являются проксиклассами, которые добавляют к обычным указателям управление ресурсами.
Полезностьпрокси-классов - давно установленный и не вызывающий сомнения факт. ФактическиSбГnава 2. Объявnение autoшаблон проектирования "Прокси" - один из наиболее давних членов пантеона шаблонов проектирования программного обеспечения.Одни прокси-классы спроектированы так, чтобы быть очевидными для клиентов.Это, например, такие классы, как s t d : : sha red_pt r и s t d : : u n i que_pt r. Другие прокси-классы спроектированы для более-менее невидимой работы. Примером такого "невидимого" прокси-класса является s t d : : vecto r<boo l > : : r e f e rence, как и его собратstd : : Ьi tset : : reference из класса std : : Ьi t se t .В этом же лагере находятся и некоторые классы библиотек С++, применяющих технологию, известную как шаблоны в111ражений (expressioп templates).
Такие библиотеки изначально разрабатывались для повышения эффективности кода для числовых вычислений.Например, для заданного класса M a t r i x и объектов rnl, rn2, mЗ и rn4 класса Matrix, выражениеMatrix surn=rnl + rn2 + mЗ + m4 ;может быть вычислено более эффективно, если ope rator+ для объектов Mat rix возвращает не сам результат, а его прокси-класс. Иначе говоря, oper a t o r+ для двух объектовMatrix должен возвращать объект прокси-класса, такого как Sum<Ma t r i x , Matrix>, а необъект Mat rix. Как и в случае с std : : vecto r <bool > : : reference и bool, должно иметьсянеявное преобразование из прокси-класса в Mat r i x , которое позволит инициализировать surn прокси-объектом, полученным из выражения справа от знака "=': (Тип этогообъекта будет традиционно кодировать все выражение инициализации, т.е.
быть чем-тонаподобие Surn<Surn<Surn<Ma t r i x , Mat ri x > , Mat rix> , Mat r i x > . Определенно, это тип,от которого следует защитить клиентов.)В качестве общего правила "невидимые" прокси-классы не умеют хорошо работатьвместе с auto. Для объектов таких классов зачастую не предусматривается существование более длительное, чем одна инструкция, так что создание переменных таких типов,как правило, нарушает фундаментальные предположения проекта библиотеки. Это справедливо для std : : vector<boo l > : : reference, и мы видели, как нарушение предположений ведет к неопределенному поведению.Следовательно, надо избегать кода следующего вида:auto someVar=выражение с типом"невидимого"прокси-кла с са ;Но как распознать, когда используется прокси-объект? Программное обеспечение, использующее невидимый прокси, вряд ли станет его рекламировать.
Ведь эти проксиобъекты должны быть невидимыми, по крайней мере концептуально! И если вы обнаружите их, то действительно ли следует отказываться от auto и массы преимуществ, продемонстрированных для него в разделе 2. 1 ?Давайте сначала зададимся вопросом, как найти прокси. Хотя "невидимые" проксиклассы спроектированы таким образом, чтобы при повседневном применении "летатьвне досягаемости радара программиста': использующие их библиотеки часто документируют такое применение. Чем лучше вы знакомы с основными проектными решениями2.2.
Есnи auto выводит нежеnатеnьный тип, испоnьзуйте явно типизированный и ни циаnизатор57используемых вами библиотек, тем менее вероятно, что вы пропустите такой прокси незамеченным.Там, где документация слишком краткая, на помощь могут прийти заголовочные файлы. Возможность сокрытия прокси-объектов в исходном коде достаточно редка. Обычно прокси-объекты возвращаются из функций, которые вызываются клиентами, так чтосигнатуры этих функций отражают существование прокси-объектов. Например, вот каквыглядит std : : vector<boo l > : : operator [ ] :namespace std {11 Из стандарта С++template <class Allocator>class vector<bool, Allocator> {puЬl i c :class reference {...};reference operator [ ] ( s i ze_type n ) ;};В предположении, что вы знаете, что operator [ ] у std : : vector<T> обычно возвращаетнеобычный возвращаемый тип у ope r a t o r [ ] в данном случае должен навести васна мысль о применении здесь прокси-класса.
Уделяя повышенное внимание используемым интерфейсам, часто можно выявить наличие прокси-классов.На практике многие разработчики обнаруживают применение прокси-классов толькотогда, когда пытаются отследить источник таинственных проблем при компиляции илиотладить никак не проходящий тесты модуль. Независимо от того, как вы его обнаружили, после того как выясняется, что auto определен как выведенный тип прокси-класса вместо "проксифицируемого" типа, решение не требует отказа от auto. Само по себеключевое слово auto проблемой не является.
Проблема в том, что auto выводит не тоттип, который вам нужен. Решение заключается в том, чтобы обеспечить вывод другоготипа. Способ достижения этого заключается в том, что я называю идиомой явной типиТ&,зации инициализатора.Идиома явной типизации инициализатора включает объявление переменной с использованием auto, но с приведением инициализирующего выражения к тому типу, который должен вывести auto.
Например, вот как можно использовать эту идиому, чтобызаставить hi ghPr i o r i t y стать переменной типа bool:auto highPriorityЗдесь=static_cast<Ьool> ( features ( w) [ 5 ] ) ;продолжает, как и ранее, возвращать объект типано приведение изменяет тип выражения на boo l , который auto затем выводит в качестве типа переменной h i g h P r i o r i t y. Во время выполнения программы объект s t d : : vector<boo l > : : refe rence, который возвращаетсявызовом s t d : : vector<boo l > : : ope rator [ ] , преобразуется в значение bool и в качествеf e a t u re s ( w ) [ 5 ]s t d : : vect o r<bool > : : refe rence,58Гnава 2 . Обьявnение autoчасти преобразования выполняется разыменование все еще корректного указателяна std : : vector<boo l > , возвращенного вызовом features.
Это позволяет избежать неопределенного поведения, с которым мы сталкивались ранее. Затем к битам, на которыеуказывает указатель, применяется индексация с индексом 5 и полученное значение типаbool используется для инициализации переменной highPriority.В примере с Ma t rix идиома явно типизированного инициализатора выглядит следующим образом:auto surnstatic cast<Мatrix> (ml + m2 + mЗ + m4) ;_=Применение идиомы не ограничивается инициализаторами, производимыми проксиклассами. Она может быть полезной для того, чтобы подчеркнуть, что вы сознательносоздаете переменную типа, отличного от типа, генерируемого инициализирующим выражением.
Предположим, например, что у вас есть функция для вычисления некоторогозначения отклонения:douЫe calcEpsilon ( ) ; / / Возвращает значение отклоненияОчевидно, что calcEps i l on возвращает значение douЫe, но предположим, что вы знаете, что для вашего приложения точности float вполне достаточно и для вас существеннаразница в размерах между f loat и douЬl e . Вы можете объявить переменную типа f loatдля хранения результата функции calcEp s i l onfloat ер = calcEpsilon ( ) ; / / Неявное преобразование/ / douЫe -> floatно это вряд ли выражает мысль "я намеренно уменьшаю точность значения, возвращенного функцией': Зато это делает идиома явной типизации инициализатора:auto ер = static cast<float> (calcEpsilon ( ) ) ;_Аналогичные рассуждения применяются, если у вас есть выражение с плавающей точкой,которое вы преднамеренно сохраняете как целочисленное значение.