С. Мейерс - Эффективный и современный C++ (1114942), страница 11
Текст из файла (страница 11)
. . но есть одна проблема. Вы ее не видите?Чтобы разобраться, что здесь не так, надо вспомнить, что часть st d : : unordered_map, содержащая ключ, является константной, так что тип s t d : : ра i r в хештаблице (которой является s t d : : u norde red_map) вовсе не s t d : : pa i r< s t d : : s t r i n g ,i n t > , а s t d : : pa i r<const s t d : : st r i n g , i n t > . Но переменная р в приведенном вышецикле объявлена иначе. В результате компилятор будет искать способ преобразоватьобъекты std : : pa i r<const std : : st ring , i nt> (хранящиеся в хеш-таблице) в объектыstd : : pa i r<std : : st r ing, int> (объявленный тип р).
Этот способ - создание временногообъекта типа, требуемого р, чтобы скопировать в него каждый объект из m с последующим52Глава 2. Объявление autoсвязыванием ссылки р с этим временным объектом. В конце каждой итерации цикла временный объект уничтожается. Если этот цикл написан вами, вы, вероятно, будете удивлены егоповедением, поскольку почти наверняка планировали просто связывать ссылку р с каждымэлементом в m.Такое непреднамеренное несоответствие легко лечится с помощью auto:for ( const auto& р". 1 1Каки:m)ранееЭто не просто более эффективно - это еще и менее многословно.
Кроме того, этоткод имеет очень привлекательную особенность - если вы возьмете адрес р, то можетебыть уверены, что получите указатель на элемент в m. В коде, не использующем auto, выполучите указатель на временный объект - объект, который будет уничтожен в концеитерации цикла.Два последних примера - запись unsi gned там, где вы должны были написать std : :vector<in t> : : s i ze_type, и запись s t d : : pa i r <std : : st ri n g , i n t > там, где вы должныбыли написать s t d : : pai r<const s t d : : st ri ng , int>, - демонстрируют, как явное указание типов может привести к неявному их преобразованию, которое вы не хотели и неждали. Если вы используете в качестве типа целевой переменной auto, вам не надо беспокоиться о несоответствиях между типом объявленной переменной и типом инициализирующего ее выражения.Таким образом, имеется несколько причин для предпочтительного примененияauto по сравнению с явным объявлением типа.
Но auto не является совершенным. Типдля каждой переменной, объявленной как auto, выводится из инициализирующего еевыражения, а некоторые инициализирующие выражения имеют типы, которые не предполагались и нежелательны. Условия, при которых возникают такие ситуации, и что приэтом можно сделать, рассматриваются в разделах 1 .2 и 2.2, поэтому здесь я не буду ихрассматривать. Вместо этого я уделю внимание другому вопросу, который может вас волновать при использовании auto вместо традиционного объявления типа, - удобочитаемость полученного исходного текста.Для начала сделайте глубокий вдох и расслабьтесь.
Применение auto - возможность,а не требование. Если, в соответствии с вашими профессиональными представлениями,ваш код будет понятнее или легче сопровождаемым или лучше в каком-то ином отношении при использовании явных объявлений типов, вы можете продолжать их использовать. Но имейте в виду, что С++ - не первый язык, принявший на вооружение то,что в мире языков программирования известно как вывод т ипов (type inference). Другиепроцедурные статически типизированные языки программирования (например, С#, D,Scala, Visual Basic) обладают более или менее эквивалентными возможностями, не говоря уже о множестве статически типизированных функциональных языков (например,ML, Haskell, OCaml, F# и др.).
В частности, это объясняется успехом динамически типизированных языков программирования, таких как Perl, Python и Ruby, в которых явнаятипизация переменных - большая редкость. Сообщество разработчиков программного2.1 . П редпочитайтеauto явному объявnению типа53обеспечения имеет обширный опыт работы с выводом типов, и он продемонстрировал,что в такой технологии нет ничего мешающего созданию и поддержке крупных приложений промышленного уровня.Некоторых разработчиков беспокоит тот факт, что применение auto исключает возможность определения типа при беглом взгляде на исходный текст.
Однако возможности IDE показывать типы объектов часто устраняют эту проблему (даже если принятьво внимание обсуждавшиеся в разделе 1 .4 вопросы, связанные с выводом типов в IDE),а во многих случаях абстрактный взгляд на тип объекта столь же полезен, как и точныйтип. Зачастую достаточно, например, знать, что объект является контейнером, счетчикомили интеллектуальным указателем, не зная при этом точно, каким именно контейнером,счетчиком или указателем. При правильном подборе имен переменных такая абстрактная информация о типе почти всегда оказывается под рукой.Суть дела заключается в том, что явно указываемые типы зачастую мало что дают, крометого что открывают возможности для ошибок - в плане как корректности, так и производительности программ. Кроме того, типы auto автоматически изменяются при изменениитипов инициализирующих их выражений, а это означает облегчение выполнения рефакторинга при использовании auto.
Например, если функция объявлена как возвращающая int,но позже вы решите, что long вас больше устраивает, вызывающий код автоматически обновится при следующей компиляции (если результат вызова функции хранится в переменной,объявленной как auto). Если результат хранится в переменной, объявленной как i nt, выдолжны найти все точки вызова функции и внести необходимые изменения.Следует запомн ить•Переменные, объявленные как auto, должны быть инициализированы; в общемслучае они невосприимчивы к несоответствиям типов, которые могут привестик проблемам переносимости или эффективности; могут облегчить процесс рефакторинга; и обычно требуют куда меньшего количества ударов по клавишам, чемпеременные с явно указанными типами.•Переменные, объявленные как auto, могут быть подвержены неприятностям, описанным в разделах 1 .2 и 2.2.2.2.
Есл и auto выводит нежелател ьный тип,испол ьзуйте явно типизированный инициал изаторВ разделе 2 . 1 поясняется, что применение auto для объявления переменных предоставляет ряд технических преимуществ по сравнению с явным указанием типов, но иногда вывод типа auto идет налево там, где вы хотите направо. Предположим, например,что у меня есть функция, которая получает Widget и возвращает std : : vect or<bool>, гдекаждый bool указывает, обладает ли Widget определенным свойством:std : : vector<bool> feature s ( const Widge t & w ) ;54Гnава 2.
Объявnение autoПредположим далее, что пятый бит указывает наличие высокого приоритета у Widget .Мы можем написать следующий код.Widget w;features (w) [ 5 ] ; / / Имеет ли w высокий11 приоритет?bool highPriorityprocessWidget (w, highPriority) ;11 Обработка w в соответ// ствии с приоритетомВ этом коде нет ничего неверного. Он корректно работает. Но если мы внесем кажущеесябезобидным изменение и заменим явный тип h i ghPriori t y типом autoauto highPriority=features (w) [ 5 ] ; // Имеет ли w высокий11 приоритет?то ситуация изменится.
Код будет продолжать компилироваться, но его поведение больше не будет предсказуемым:processWidget (w, highPriority) ; /! Неопределенное поведение !Как указано в комментарии, вызов proces sWidget теперь имеет неопределенное поведение.
Но почему� Ответ, скорее всего, вас удивит. В коде, использующем auto, типhighPriority больше не является boo l. Хотя концептуально std : : vector<boo l> хранитзначения bool, ope rator [ ] у s t d : : vector<bool > не возвращает ссылку на элемент контейнера (то, что s t d : : vector : : operator [ ] возвращает для всех типов за исключениемbool) . Вместо этого возвращается объект типа std : : vector<bool > : : refe rence (класса,вложенного в std : : vector<bool > ).Тип s t d : : vect or<boo l > : : re f e rence существует потому, что s t d : : ve c to r<boo l >определен как хранящий значения bool в упакованном виде, п о одному биту на каждоезначение.
Это создает проблему для оператора operator [ ] класса s t d : : vector<bool>,поскольку ope ra t o r [ J класса s t d : : ve c t o r < T > должен возвращать Т & , но С++ запрещает ссылаться на отдельные биты. Будучи не в состоянии вернуть b o o l & ,operator [ ] класса s t d : : vector<bo o l > возвращает объект, который действует подобно boo l & . Для успешной работы объекты s t d : : ve c t o r<boo l > : : re ference должны быть применимы по сути во всех контекстах, где применим boo l & .
Среди прочихвозможностей s t d : : vecto r<boo l > : : r e f e rence обладает неявным преобразованиемв boo l. (Не в boo l & , а именно в boo l . Пояснение всего набора методов, используемыхstd : : vector<bool > : : refe rence для эмуляции поведения boo l & , завело бы нас слишкомдалеко, так что я просто замечу, что это неявное преобразование является только однимиз камней в существенно большей мозаике.)С учетом этой информации посмотрим еще раз на следующую часть исходного кода:bool highPriority=features ( w ) [ 5 ] ; 11 Явное объявление типа/ / highPriority2.2. Если auto выводит неже л ательный тип, используйте явно типизированный инициализатор55Здесь f e a t u r e s возвращает объект s t d : : ve c t o r <boo l > , для которого вызываетсяope r a t o r [ ] .
Этот оператор возвращает объект типа s t d : : vector<bool > : : r e f e rence,который затем неявно преобразуется в значение типа bool, необходимое для инициализации highPriori t y. Таким образом, highPri ority в конечном итоге получает значениепятого бита из std : : vect or<bool >, возвращенного функцией features, так, как и предполагалось.Но что же произойдет, если переменная highPriority будет объявлена как auto?auto highPriority=features ( w ) [ 5 ] ; / / Вывод типа highPriorityФун кция f e a t u r e s , как и ранее, возвращает объект типа s t d : : v e c t o r < b o o l > ,и , как и ранее, выполняется его o pe r a t o r [ ] . О ператор возвращает объект типаs t d : : ve c t o r<boo l > : : r e f e r e n c e , но дальше привычный ход событий изменяется,так как auto приводит к выводу типа переменной h i ghPr i o r i t y.
Теперь переменнаяhighPrior i t y не получает значение пятого бита std : : vector<bool>, возвращенного вызовом features.Полученное ею значение зависит от того, как реализован тип std : : vector<boo l > : :reference. Одна из реализаций таких объектов состоит в том, чтобы содержать указатель на машинное слово с интересующим нас битом и смещение этого бита в слове. Рассмотрим, что это означает для инициализации highPriority, в предположении, что имеет место именно такая реализация std : : vector<boo l > : : re ference.Вызов f e a t u r e s возвращает временный объект s t d : : v e c t o r<boo l > .