|
Программирование >> Обобщенные обратные вызовы
Задача 29. Инициализация ли это? Сложность: 3 в этой задаче мы рассмотрим такой вопрос: Что если мы инициализировали объект, и ничего не произошло? В задаче предполагается, что все соответствующие стандартные заголовочные файлы включены и сделаны все необходимые usi пд-объявления. Вопрос для новичка 1. Что делает следующий исходный текст? deque<string> colli; copyCistream iterator<string>( cin ), i streanui terator<stri ng>0 , back inserter( colli ) ); Вопрос для профессионала 2. Что делает следующий исходный текст? deque<string> со112( col 11.begiп(), colli.end() ); deque<string> coll3( istream iterator<string>( cin ), istream iterator<string>0 ); 3. Что следует изменить в программе, чтобы она работала так, как, вероятно, ожидает программист? Решение Базовый механизм заполнения 1. Что делает следующий исходный текст? пример 29 1(a) deque<string> colli; copy (istrearTL-i terator <string>( cin ), istreanuiterator <stri ng>0 , back inserter( col11 ) ); В приведенном фрагменте объявляется изначально пустой дек строк с именем colli. Затем выполняется заполнение путе.м копирования строк, разделенных пробельными символами, из стандартного потока ввода cin в дек с использованием функции deque: :push back, пока не закончится весь входной поток. Исходный текст примера 29-1(a) эквивалентен следующему. пример 29-1(6): Эквивалентная запись 29-1(а) deque<string> colli; istream iterator<strinq> fi rst( cin ), last; copy( fi rst, last, bacK inserter( col 11 ) ); Единственное отличие от исходного текста 29-1(a) состоит в том, что в нем обьекты istream iterator создавались на лету , как временные неименованные, так что они уничтожались по завершении вызова сору. В примере 29-1(6) объекты i s-tream iterator являются именованными переменными и благополучно переживают вызов сору, они не будут уничтожены до тех пор, пока не будет достигнут конец области действия, в которой находится исходный текст примера 29-1(6). Не инициализация 2. Что делает следующий исходный текст? пример 29-2(а) deque<string> соП2 ( colli, begin С), col 11. end () ) ; В этом коде объявлен второй дек строк с именем соП2, и выполняется его заполнение с помощью подходящего конструктора. Здесь использован конструктор deque, который принимает пару итераторов, соответствующих диапазону, из которого должно выполняться копирование. В представленном исходном тексте col 12 инициализируется диапазоном итераторов, который соответствует всему содержимому colli . Код примера 29-2(а) практически эквивалентен следующему. пример 29-2(6): практически то же, что и в 29-2(а) дополнительный шаг: вызов конструктора по умолчанию deque<string> coll2; Добавление элементов с использованием push back сору( colli.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<stri ng> по значению и получает два параметра: 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 не имеют ничего общего, кроме одинакового написания. Программисты время от времени сталкиваются с этой проблемой в своей работе, поэтому мы и отвели для ее рассмотрения отдельную задачу. Поскольку рассмотренный исходный текст, как это ни удивительно, представляет собой всего лишь объявление функции, реально он ничего не делает - для него компилятором не генерируется никакого кода, не выполняются никакие действия - не вызываются конструкторы дека, не создаются объекты. Корректное заполнение Было бы нечестно бросить вас на полпути, указав на проблему и не показав ее решения. А потому - последняя часть задачи:
|
© 2006 - 2024 pmbk.ru. Генерация страницы: 0
При копировании материалов приветствуются ссылки. |