С. Мейерс - Эффективный и современный C++ (1114942), страница 13
Текст из файла (страница 13)
Предположим, чтовам надо вычислить индекс элемента в контейнере с итераторами произвольного доступа (например, s t d : : vector, s t d : : deque или s t d : : a rray} и вы получаете значение типаdouЬle между О . О и 1 . О, указывающее, насколько далеко от начала контейнера расположен этот элемент (О . 5 указывает на середину контейнера). Далее, предположим, что выуверены в том, что полученный индекс можно разместить в int. Если ваш контейнерс, а значение с плавающей точкойd, индекс можно вычислить следующим образом:--int index=d * ( c . s i ze ( )-1) ;Но здесь скрыт тот факт, что вы преднамеренно преобразуете douЫe справа от знака "="в int.
Идиома явно типизированного инициализатора делает этот факт очевидным:auto index=static cast<int> (d * ( c . si ze ( )_-1) ) ;2.2. Если auto выводит нежелательный тип, используйте явно типизированный и н и циализатор59Сnедует запомнить•"Невидимые" прокси-типы могут привести auto к выводу неверного типа инициализирующего выражения.•Идиома явно типизированного инициализатора заставляет auto выводить тот тип,который нужен вам.бОГnава 2. Обьявnение autoГЛАВА 3Перех од к с о в ременном у С++Когда дело доходит до умных терминов, С++ 1 1 и С++ 1 4 есть чем похвастаться.
auto,интеллектуальные указатели, семантика перемещения, лямбда-выражения, параллелизм - каждая возможность настолько важна, что я посвящаю ей отдельную главу. Оченьважно освоить все эти возможности, но большой путь к эффективному программированию на современном С++ требует множества маленьких шажков. Каждый шаг отвечаетна конкретные вопросы, возникающие во время путешествия от С++98 к современномуС++.
Когда следует использовать фигурные скобки вместо круглых для создания объектов? Почему объявление псевдонимов лучше, чем применение t ypedef? Чем constexprотличается от const? Как связаны константные функции-члены и безопасность с точкизрения потоков? Этот список можно продолжать и продолжать. В этой главе постепенно,один за другим, даются ответы на эти вопросы.3.1 .
Разпичие между { } и ( ) при создании объектовВ зависимости от вашей точки зрения выбор синтаксиса для инициализации объектов в С++ 1 1 либо очень богатый, либо запутанный и беспорядочный. Как правило, инициализирующие значения указываются с помощью круглых скобок, знака равенства илифигурных скобок:11 Инициализатор в круглых скобкахint х ( О ) ;int у = О ; // Инициализатор после " = "int z { О } ; // Инициализатор в фигурных скобкахВо многих случаях можно использовать знак равенства и фигурные скобки одновременно:intz = { О } ; // Инициализатор использует "=" и фигурные скобкиВ оставшейся части данного раздела я в основном буду игнорировать синтаксис, в котором одновременно используются знак равенства и фигурные скобки, поскольку С++обычно трактует его так же, как и версию только с фигурными скобками.Сторонники "полного беспорядка" указывают на то, что применение знака равенствадля инициализации часто сбивает с толку новичков в С++, которые считают, что имеютдело с присваиванием, хотя на самом деле это не так.
Для встроенных типов наподобиеi nt эта разница носит чисто академический характер, но в случае пользовательских типов очень важно отличать инициализацию от присваивания, поскольку при этом вызываются различные функции:Widget wl ;Widget w2wlw2 ;=/ / Вызов конструктора по умолчаниюwl ; / / Не присваивание, а копирующий конструктор/ / Присваивание ; вызов оператора operator= ( )Даже при наличии нескольких синтаксисов инициализации существовали определенныеситуации, когда в С++98 не было возможности выразить желаемую инициализацию. Например, было невозможно прямо указать, что контейнер STL должен быть создан содержащим определенный набор значений (например, 1 , 3 и 5).Для устранения путаницы из-за нескольких синтаксисов инициализации и решенияпроблемы охвата всех сценариев инициализации С++ 1 1 вводит унифицированную инициализацию (uпiform initializatioп): единый синтаксис инициализации, который может, какминимум концептуально, использоваться везде и выражать все.
Он основан на фигурныхскобках, и по этой причине я лично предпочитаю термин "фигурная инициализация"(braced initialization). Унифицированная инициализация - это идея. Фигурная инициализация - это синтаксическая конструкция.Фигурная инициализация позволяет выразить то, что было невозможно выразить ранее.
С помощью фигурных скобок легко указать начальное содержимое контейнера:std: : vector<int> v{ 1 , 3, 5 } ; // v иэначально содержит 1 , 3, 5Фигурные скобки могут также использоваться для указания значений инициализациипо умолчанию для нестатических членов-данных. Эта возможность - новая в С++ 1 1 может использоваться с синтаксисом "=': но не с круглыми скобками:class Widgetprivate :int х { О } ; / / ОК, эначение х по умолчанию равно Оint у = О ; 1 1 Тоже ОК/ / Ошибка !int z ( O) ;};С другой стороны, некопируемые объекты (например, s t d : : a tomi cсм.
раздел 7.6)могут быть инициализированы с помощью фигурных или круглых скобок, но не с помощью знака равенства:-std : : atomic<int> ai l { О } ; // ОК/ / ОКstd : : atomic<int> ai2 ( 0 ) ;std: : atomic< int> a i 3 = О ; 1 1 Ошибка !Легко понять, почему фигурная инициализация названа "унифицированной''. Из трехспособов обозначения выражений инициализации только фигурные скобки могут использоваться везде.62Гnава 3 . Переход к современному ( ++Новая возможность фигурной инициализации заключается в том, что она запрещаетнеявные сужающие преобразования среди встроенных типов. Если значение выраженияв фигурном инициализаторе не может быть гарантированно выражено типом инициализируемого объекта, код не компилируется:douЫeх,int suml {у, z ;х+у + z } ; / / Ошибка ! Сумма douЫe может/ / не выражаться с помощью intИнициализация с использованием круглых скобок и знака равенства не выполняет проверку сужающего преобразования, поскольку это может привести к неработоспособности большого количества старого кода:int sum2 ( x+y+ z ) ; // ОК ( значение выражения усекается до int )iпt sumЗ = x+y+ z ; / /Обращает на себя внимание еще одна особенность фигурной инициализации - онане подвержена наиболее неприятному анализу в С++.
Побочным эффектом правила С++,согласно которому все, что в ходе синтаксического анализа может рассматриваться какобъявление, должно рассматриваться как таковое, является так называемый наиболее неприятный анализ, который чаще всего досаждает разработчикам, когда они хотят создатьобъект по умолчанию, а в результате получают объявление функции. Корень проблемыкроется в том, что если вы хотите вызвать конструктор с аргументом, вы делаете это примерно следующим образом:Widget wl (lO) ; // Вызов конструктора Widget с аргументом10Но если в ы пытаетесь вызвать конструктор Widget без аргументов с помощью аналогичного синтаксиса, то фактически объявляете функцию вместо объекта:Widget w2 ( ) ; / / Синтаксический анализ рассматривает зто как11 объявление функции w2 , возвращающей Widget 1Функции не могут быть объявлены с использованием фигурных скобок для списка параметров, так что конструирование объекта по умолчанию с применением фигурных скобок такой проблемы не вызовет:Widget wЗ { ) ; / / Вызов конструктора Widget без аргументовТаким образом, в пользу фигурной инициализации имеется много "за".
Это синтаксис,который может использоваться в самых разнообразных контекстах, предотвращающийнеявные сужающие преобразования и не подверженный неприятностям с синтаксическим анализом С++. Тройное "за"! Так почему бы не озаглавить раздел просто "Используйте синтаксис фигурной инициализации"?Основной недостаток фигурной инициализации - временами сопровождающее ееудивительное поведение. Такое поведение вырастает из необыкновенно запутанных взаимоотношений между фигурной инициализацией, st d : : i n i t i al i zer_ l i s t и разрешением перегрузки конструкторов. Их взаимодействие может привести к коду, который, как3.1 . Различие между {} и () при создании объектов63кажется, должен делать что-то одно, а в результате делает что-то совсем другое.
Например, в разделе 1 .2 поясняется, что когда переменная, объявленная как auto, имеет инициализатор в фигурных скобках, то выводимым типом является std : : i n i t i a l i z e r_l i st,несмотря на то что другой способ объявления переменной с тем же инициализаторомдаст более ожидаемый тип. В результате чем больше вам нравится auto, тем меньше энтузиазма вы должны проявить по отношению к фигурной инициализации.В вызовах конструктора круглые и фигурные скобки имеют один и тот же смысл, покав конструкторах не принимают участие параметры std : : ini t i a l i zer_ l i s t :class Widget {puЫ i c :Widget ( int i , bool Ь ) ;/ / Конструкторы н е имеют параметровWidget (int i , douЫe d ) ; // std : : initiali zer l i st};WidgetWidgetWidgetWidgettrue) ; 1 1 Вызовtrue } ; 1 1 Вызов1 1 Вызовw3 ( 1 0 , 5 .
0 ) ;11 Вызовw4 { 1 0 , 5 . 0 } ;wl ( l O ,w2 { 1 0 ,первогопервоговтороговторогоконструктораконструктораконструктораконструктораЕсли же одини л и несколько конструкторов объявляют параметр типавызовы, использующие синтаксис фигурной инициализации,строго предпочитают перегрузки, принимающие s t d : : init i a l i zer_l i s t . Строго. Еслиу компилятора есть лю бой спосо б истолковать вызов с фигурным инициализатором какконструктор, принимающий s t d : : i n i t i a l i ze r_ l i s t , он использует именно это толкование. Если класс W i dg e t выше дополнить конструктором, принимающим, например,std : : i n i t i a l i ze r l i s t ,_s td : : i n i t i a l i ze r l i st < l ong douЫe>_class Widget (puЫ i c :Widget ( int i , bool Ь ) ;Widget ( int i , douЫe d ) ;11 Как и ранее11 Как и ранееWidget (std: : initializer list<long douЫe> il) ; // Добавлен_};то w2 и w4 будут созданы с помощью нового конструктора, несмотря на то что типэлементов s t d : : i п i t i a l i z e r l i s t (в данном случаеlong douЫe ) хуже соответствует обоим аргументам по сравнению с конструкторами, не принимающимиst d : : ini t i a l i ze r l is t ! Смотрите сами:_-Widget wl ( l O , true) ; / / Использует круглые скобки и, как и11 ранее, вызывает первый конструкторWidget w2 { 1 0 , true } ; / / Использует фигурные скобки, но теперь64Глава 3 .
Переход к современному С++1 1 вызывает третий конструктор11 (10 иtrue преобразуются в long douЬle )Widget wЗ ( 1 0 , 5 . О) ;1 1 Использует круглые скобки и, как иWidget w4 { 1 0 , 5 . 0 } ;1 1 Использует фигурные скобки , НО теперь1 1 ранее, вьGывает второй конструктор1 1 вызывает третий конструктор11 (10 и5 . 0 преобразуются в long douЬl e )Даже то, что в обычной ситуации представляет собой копирующий или перемещающийконструктор, может быть перехвачено конструктором с s td : : i n i t i а l i zer_l i s t :class Widget {puЬli c :Widget ( int i , bool Ь ) ;/ / Как ранееWidget ( int i , douЫe d) ;/ / Как ранееWidget ( s td : : init ializer_list<long douЬle> i l ) ; / / Как ранееoperator float ( ) const;1 1 Преобразование воfloat};Widget w5 (w4 ) ;1 1 Использует круглые скобки , вызывает1 1 копирукщий конструкторWidget wб{w4 } ;1 1 Использует фигурные скобки, вызов1 1 конструктора сs td : : initiali zer_list( w4 преобразуется в о floa t , а float1 1 преобразуется в long douЬle )11Widget w7 ( std : : move (w4 ) ) ; 1 1 Использует круглые скобки, вызывает1 1 перемещающий конструкторWidget w8 { std: : move (w4 ) } ; 1 1 Использует фигурные скобки, ВЬGОВ1 1 конструктора с std : : initiali zer_l ist1 1 ( все, как для w б )Определение компилятором соответствия фигурных и нициализаторов конструкторам с std : : i п i t i a l i z e r_ l i s t настолько строгое, что доминирует даже тогда, когда конструктор с s t d : : in i t i a l i ze r_l i s t с наилучшим соответствием не может быть вызван,например:class WidgetpuЬli c :Widget ( int i , bool Ь ) ;/ / Как ранееWidget ( int i , douЬle d) ; / / Как ранееWidget ( std : : initiali zer_l ist<Ьool > i l ) ; / / Теперь тип1 1 элемента-bool/ / Нет функций неявного преобразованияWidget w{lO , 5 .