Анимация
JavaScript


Главная  Библионтека 

0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 [ 59 ] 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85

Не инициализация

2. Что делает следующий исходный текст?

пример 29-2(а)

deque<string> cell 2( colli.begin С), colli.end() );

В этом коде объявлен второй дек строк с именем соП2, и выполняется его заполнение с помощью подходящего конструктора. Здесь использован конструктор deque, который принимает пару итераторов, соответствующих диапазону, из которого должно выполняться копирование. В представленном исходном тексте col 12 инициализируется диапазоном итераторов, который соответствует "всему содержимому colli".

Код примера 29-2(а) практически эквивалентен следующему.

пример 29-2(6): практически то же, что и в 29-2(а)

дополнительный шаг: вызов конструктора по умолчанию deque<string> coll2;

Добавление элементов с использованием push back

соруС colU.beginO , col ll.endO , back inserter( col 12 ) ) ;

Единственное (небольшое) отличие заключается в том, что сначала вызывается конструктор по умолчанию col 12, а затем вставка элеменгов в контейнер с использованием функции push back выполняется как отдельный шаг программы. Первоначальная версия кода выполняет все это при помощи конструктора, в который передается пара итераторов, которая, вероятно (хотя и не обязательно) означает то же самое, что и ранее.

Возможно, вы удивитесь, что я растолковываю очевидные вещи. Причина станет понятной, когда мы рассмотрим последнюю часть исходного текста. К сожалению, в нашем случае код ведет себя не так, как от него ожидают.

Пример 29-2(в): Объявление еще одного дека?

deque<string> со113( istream iterator <string>( cin ),

istream i terator <string>0 );

Код выглядит и ведет себя на первый взгляд так же, как и в примере 29-1(a), а именно - создаст дек строк, который заполнясгся из стандартного потока ввода, с тем отличием, что он пытается использовать синтаксис из примера 29-2(а), а именно - воспользоваться конструктором с диапазоном итераторов, передаваемым при помощи параметров.

В этом исходном тексте есть одна потенциальная и одна реальная проблемы. Потенциальная проблема заключается в том, что стандартный ввод cin полностью поступает в контейнер, так что входные данные, необходимые для ввода в другой части программы, могут оказаться считанными в контейнер, что может вызвать определенные логические проблемы.

Однако самая большая проблема в том, что на самом деле приведенный исходный текст ничего этого не делает. Почему? Потому что это - не объявление объекта col 13 типа deque<string>. На самом деле это объявление (вдохните побольше воздуха) функции с именем col 13,

которая возвращает deque<string> по значению

и получает два параметра:

istream iterator<string> с именем формального параметра cin, и функцию без имени формального параметра*,

Само собой разумеется, при этом происходит преобразование имени функции в указатель на нее. - Прим. ред.



которая не имеет параметров и возвращает istream„i terator<string>. (Попробуйте-ка произнести это как скороговорку.)

Что же здесь происходит? Мы имеем дело с тяжким наследием С, правилом, которое оставлено для обеспечения обратной совместимости: если часть кода может быть интерпретирована как объявление, она и является объявлением. Процитируем стандарт С + +:

В грамматике имеется неоднозначность, когда инструкция может быть как выражением, так и объявлением. Если выражение с явным преобразованием типов в стиле вызова функции ( expr.type.conv ) является крайним слева, то оно может быть неотличимо от объявления, в котором первый оператор объявления начинается с открывающей круглой скобки "(". В этом случае инструкция рассматривается как объявление. -- [С++03] §6.8

Не вдаваясь в детали, скажем, что причина возникновения этого правила - стремление помочь компиляторам при работе с жутким синтаксисом объявлений в С, который может оказаться неоднозначным. Чтобы компилятор мог справиться с этими неоднозначностями, введено универсальное положение - "если ты в сомнении - это объявление".

Если вы еще не убеждены, то взгляните на задачу 42 из [SutterOO] (задача 10.1 в русском издании), где есть похожий, но более простой пример. Давайте разберем объявление щаг за шагом, чтобы понять, что оно означает.

Пример 29-2(г): Идентичен примеру 29-2(в), с удалением излишних скобок и добавлением typedef

typedef istream iterator<string> (Func)() ;

deque<stnng> соПЗ( istream i terator<string> cin, Func ) ;

Это более похоже на объявление функции? Может, да, может, нет -- так что давайте сделаем следующий щаг и уберем имя формального параметра cin, которое все равно игнорируется, и изменим имя col 1 3 на нечто более похожее на обычное имя функции:

пример 29-2(д): идентичен примеру 29-2(в), с небольшими

изменениями имен

typedef istream iterator<string> (Func)(); deque<string> f( istream„iterator<string>, Func );

Теперь все становится понятно. Это "может быть" объявлением функции, так что в соответствии с синтаксическими правилами С и С++, оно таковым и является. С толку сбивает то, что оно выглядит очень похоже на синтаксис конструктора, а имя формального параметра cin (которое идентично имени переменной, находящейся в области видимости, и даже определено стандартом) и вовсе запутывает дело. Но в данном случае это не имеет значения, так как имя формального параметра и std: :cin не имеют ничего общего, кроме одинакового написания.

Программисты время от времени сталкиваются с этой проблемой в своей работе, поэгому мы и отвели для с с рассмотрения отдельную задачу. Поскольку рассмотренный исходный текст, как это ни удивительно, представляет собой всего лишь объявление функции, реально он ничего не делает - для него компилятором не генерируется никакого кода, не выполняются никакие действия - не вызываются конструкторы дека, не создаются объекты.

Корректное заполнение

Было бы нечестно бросить вас на полпути, указав на проблему и не показав ее решения. А потому - последняя часть задачи:



з. Что следует изменить в программе, чтобы она работала так, как, вероятно, ожидает программист?

Все, что нам надо, - внести в исходный текст такие изменения, чтобы компилятор не мог рассматривать сго как объявление функции. Есть два способа сделать это. Вот более привлекательный способ,

пример 29-3(а): устранение неоднозначности при помощи дополнительной пары скобок (неплохое

решение; оценка 7/10)

deque<strTng> col 13((i stream.,!terator<stri ng>(ci n)) , istream iterator<string>() );

Здесь мы просто добавили дополнительные скобки вокруг параметра, давая понять компилятору, что мы хотим, чтобы это был параметр конструктора, а не объявление параметра функции. Этот способ срабатывает, поскольку хотя istream iterator<string>(cin) может быть объявлением переменной (или, как уже упоми-налось, объявлением параметра), (istream iterator<string>(cin)) объявлением параметра быть не может. Соответственно, исходный текст примера 29-Зя не может быть объявлением функции по той же причине, по которой не может быть объявлением функции void f( (int i) ), a именно - из-за наличия дополнительных скобок, которые не могут окружать параметр в объявлении функции.

Имеются и другие способы разрешения неоднозначности, которые делают невозможной интерпретацию указанного выражения как объявления функции, но я не буду приводить их здесь по очень простой причине: чтобы эти способы сработали, и вы, и ваш компилятор должны очень хорошо понимать этот "темный угол" стандарта языка.

> Рекомендация

Избегайте "темных углов" языка программирования, включая вполне корректные конструкции, которые, тем не менее, способны сбивать с толку не только программистов, но даже компиляторы.

Описанная неоднозначность синтаксиса по своей природе похожа на острие ножа, по которому лучше не ходить вовсе. а потому лучше полностью избегать использования такого синтаксиса. Я предпочитаю сам и настойчиво рекомендую вам другой способ, существенно более простой и понятный даже не слишком умным компиляторам

и, кроме того, облегчающий чтение и понимание текста человеком.

Пример 29-3(6): использование именованных переменных (рекомендуемое решение; оценка 10/10)

istream iterator<string> fi rst( cin ), last; deque<string> coll3( first, last );

Как в примере 29-Зя, так и в при.мере 29-36 достаточно внести предлагаемые изменения только в один параметр, но чтобы быть последовательным, я сделал именованными оба параметра.

> Рекомендация

В качестве аргументов конструктора лучше использовать именованные переменные. Это позволяет избежать возможной неоднозначности между вызовом конструктора и объявлением функции. Кроме того, зачастую это делает более понятным назначение вашего кода и облегчает тем самым его поддержку.



0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 [ 59 ] 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85