Б. Страуструп - Язык программирования С++. Специальное издание, 3-изд. Бином. 2004 (1160791), страница 56
Текст из файла (страница 56)
*/ ) сл1таш ]ш1 аеас, сьаг' аг!!о)]) ! /* ... "! ) Для того чтобы функция спа!и () распознавалась в качестве главной, она должна бсять глобзльной, поэтому здесь не используется прогтранство имен. Физическая структура системы ьсо>кет быть представлена следующим образом: 'е Ве>р )с<' с)) Обратите внимание, что все заголовочные файлы в верхнем ряду явля!отса средствами стандартной библиотеки.
Во многих случаях прп анализе зтп библиотеки можно проигнорировать, потому что они хорошо известны и стабильны. Б крошечной программе структура может быть упрощена путем помещения всех директив о !пс!ис!е в общий заголовочный файл. Этот стиль физического разбиения с единственным заголовочным файлом наиболее полезен, кос да программа имеет небольшой рззмер и не подразумевается отдельного использования ее частей. Обратите внимание, что когда применяются пространства имен, логическая структура программы все еще отражается в с!с, Ь.
Если пространства 256 Глава йзнсходные файлы и программы имен не исгкользуются, то структура становится менее понятной. В этом случае могут быть полезны комккентарни. Для болыппх программ подход с единственным заголовочныкч файлом неработоспособен в обычной среде разработки, ориентированной на файлы. Изменения в общем ваго.ловочном файле вызывают перекомпиляцию всей программы, а модификация одного заголовочного файла несколькпмн прозрамккистами провоцирует ошибки.
Если не делается акцент на стиле программирования с активным использованием пространств имен и классов, логическая структура ухудшается с ростом программы. 9.3.2. Несколько заголовочных файлов Альтернативной физической организацией является создание для каждого логическогого модуля своего заголовочного файла, определяющего реализуемые в нем средства. То есть у каждого .с файла имеется соответствующий .Ь фзйл, определяющий его интерфейс. Каждый .с файл включает свой .й файл и еще несколько других .й файлов, определя1оших, что ему требуется от других модулей для реализации средств, объяв- лепных в его интерфейсе.
Физическая организация соответствует логическоп организации модуля. Интерфейс для пользователей помегцен в .Ь файл, интерфейс для разработчиков выносится в файл, оканчивающийся на !тр! 'и, а определения функций модуля, его переменных н т, д, записываются в.с файлы. В этом случае синтаксический анализатор будет представлен тремя файлами: О ригвегзк О интерфейс для пользователей патеврасе Рагвег( доиЫе ехрг (Ьоо! ие1), ) Совместно используемое окру>кение для функций, реализующих анализатор, представлено файлом рагвег !тр(,й: у!рагяег ипр! Ь.
и!пс!иде "рагвег.Ь' и(пс!иде "еггог.!К и!пс!иде "!ехе~ Ь" г'Д интерфейс для разработчиков патеврасе Рагвег( с!оиЫе рпт (Ьоо! яе11 г!оиЫе гегт (Ьоо! де!)' с!оп Ые ехрг (Ьо о! де1); ив!пд йехегуе1 1одеп: ив!пяйехег; сиге 1оЫ 11ользовательский заголовочный файл рагвег!з включен для того, чтобы предоставить возможность компилятору проверить согласованность Я 9.3.1). Функции, реализующие анализатор, хранятся в рагвег,с вместе с директивами 11!пс!ис(е для заголовочных файлов, требуемых функциями Рагвег: Л'!затесе: и!пс!иде "рагвег !тр!Х' 257 9.3.
Использование заголовочных файлов и1ие1ийе 1иЫе.Ь' е1оиЫе Ригеег:рпт )Ьоо1 йе1) (1* .. '1 ) е1оиЫе Рогеег 1егго )Ьоо! де1) ( 1и .. 'у) е1оиЫе Рогхег.ехрг )Ьоо1 де1) ) !* ... */ ) В графическом виде, анализатор и его использование драйвером выглядят следующим образом: Как мы того и хотели, существует тесная связь с логической структурой, описанной в Ь 8.3.3. Для упрощения этой структуры мы моглп бы поместить включение 1аЫе.Ь в рагзег 1трЬЬ, вместо рагэег.с.
Однако 1аЫе.Ь не обязан выражать совместное нспользовзнпе функций синтаксического анализатора; этот заголовочный файл требуется только для реализации функций анализатора. В действительности, оц используется только одной функцией, рг1т (), поэтому, если бы мы задались целью свести к минимуму зависимости, мы поместили бы рпт () в собственный .с файл и включили 1аЫе.Ь только туда; Подобная тщательность годится только в случае больших модулей. В модулях реального размера обычной практикой является включение при необходимости дополнительных файлов для отдельных функций.
Волее того, не редко создается более одного файла 1тр1.Ь, потому что различным подмножествам функций модуля требуется различный совместно используемый контекст. Обратите внимание, что форма записи 1тр1.Ь не является стандартом и более того, нельзя сказать, что она широко распространена; просто мне нравится такой стиль задания имен. Зачем усложнять ссбе жизнь схемой с несколъкнми заголовочнылеп файламп? Очевидно, что требуется гораздо меньше интеллектуальных усилий для помещения всех объявлений в единственный файл, как это было сделано с Нс.Ь. Декомпозиция па несколько заголовочных файлов начинает играть значительную роль в модулях, которые в несколько раз больше нашего игрушечного синтаксического анализатора, п процзаммах, в несколько раз больших нашего калькулятора. Фундаментальная идея, которая лежит в декомпозиции такого типа, состоит в том, что она обеспечивает лу ппую локализацию.
Во время анализа и модификации болыпой програм- Глава 9.Исходные файлы и программы 258 мы программисту важно сосредоточить внпл~анпе на относительно небольших фрагментах кода. Декомпозиция на несколько ззголовочных файлов позво ласт легче определить, от чего зависит код анализатора и проигнорировать остальную часть программы. Вариант с единственным заголовочным файлом вынуждает нас просматривать все обьявлепия, используемые каждым модулем, и решать, какие нз них сушественны, Простая истина состоит в том, что сопровождение кода всегда происходит при напиши неполной ппфорыацьги и локальных знаний.
Лекомпозиция на несколысо файлов ~тозволяет пам успешно применять л~етод «изнутри наружу», имея только локальное представление о программе. Магон с единым заголовочным файлом — как и любой другой метод, основанньш па глобальном хранилшде информации, — требует подхода «сверху вниз» и заставляет нас бесконечно выяснять, что от чего зависит. Локализация ведет к сокращению информации, требуемой для компиляции модуля и, как следствие, к меньшему времени компиляции. Разница может оыть огромной. Я наблюдал, как время колшиляции уменьшалось в десять раз в результате простого анализа зависимостей, приведшего к лучшему использованию заголовочных файлов. 9.3.2.1.
Остальные модули калькулятора 11 еггосй патехрисе Еггог1 хггис'1 Хего <1гипуе ) ), х)гисг лунгах еггог) сопх1 сдпг" р; Вупгах еггог )сопв1 гд ах* у) (р = д; ) Лексический анализатор предоставляс г болыпнй по размеру и более запутанньш ин- терфейс: !! 1етегос №1пс)ис)е «вгг1пд> патехрасе1.ехег) спит Тодеп иа)ие ( МАМЕ, ХЮМВЕВ, РЕ118=' -', М11Уи8=' — ', РИНТ=",, А85161«'='=', ); Е)«хз, МШ; — "', 111У='/', Ерги )', ЕРы)' ех1егп Тодеп оп1ие сигг 1оК сх1егп г1оиЬ)е питЬег иа1ие; Оставшиеся модули калькулятора люжно организовать аналогично модулям синтаксического анализатора. Однако, онп настолько малы, что не требуют собственных файлов 1шр1.й. Такие файлы требуются только тогда, когда логический модуль состоит из большого числа функций, которыл~ требуется совместно используемый контекст.
Обработчик ошибок сократился до набора типов исключений, поэтому еггог.с не требуется: 259 9.3, Использование заголовочных файлов ех1егп з1<1<з1пад з1ппд еа!ие, То!ееп оЫие де1 1ойеп ((; Реализация лексического анализатора, кроме 1ехег !й зависит от еп опб, <1оз1 еат> и функций, определяющих тип символов, объявленных в <сс1уре>: 0 1ехегх.
№!пс!ис!е '1ехег Ь' №!пс!иИе епог.б" №!пс!иае <1оз1геат> №!пс!иЫе <сс1ире> 1.ехег:Тойеп оа!ие йехег:сигг 1оК г!оиЫе !.ехег литбег оа!ие, з1<1: з1ппдТ.ехег: зггупд оа!ие; Еехег Тобеа оа !ие Т.ехег.де1 1обеп (! (,И ... "1 1 Мы могли бы выделить директиву 11!пс!ис(е "еггог Тг" в файл !тр! !г лекснческого анализатора. Однако я решил, что это уже слишком для такой крошечной программы. Как обычно, мы включаем интерфейс модуля — в нашем с.тучае 1ехег!г — в реализацию модуля для того, чтобы кол~пилятор мог проверить согласованность.
Таблица символов вполне самодостаточна, хотя заголовочньш файл стандартной библиотеки <тар> предоставляет множество вещей, шпересных с точки зрения реализации зффс ктивного шаблона тар; 11 !аЫе. Ь №глс!иг!е <тар> №!пс!ис(е <з1 !пд> ехгегл зЫ тар<зйсз1ппа, ЫоиЫе> 1аЫе; Так как мы предполагаем, что каждь|й заголовочный файл может быть валю 1еи в несколько .с файлов, мы должны отделить объявление 1аЫе от реализации несмотря иа то, что разница между(аЫе.с и 1аб(е.й состоит в единственном ключевом слове ех1егп: /1 гаые.с: №!пс!игХе '1аЫе !И зй!..тар<зЫ, згплд, <!оиб!е 1аб!е, Драйвер зависит от всех модулей: №тс!иае рагзегб' №!пс!ийе "!ехег Ь' №!пс1ис!е "еггогб' №!пс!ис!е "1аЫе Ь' патезрасеТЗг!лег( 1п1ло о!' епоггп сгс!"!зггеа~п'1при1; ооЫзЫр ((; 260 Глава 9.Исходные файлы и программы Ф!пс!иве <ззсгеат> (л! та!п (!и! агдс, сЛаг' пгдиЯ ( /' ... '/1 Так как пространство имен !гг!пег используется исключительно функцией та!и 0, я поместил его в тп!л.с.
В качестве альтернативы я мог поместить его в с(г!пег.Ь и произвести вкл|очение. В большой системе обычно имеет смысл организовать модули так, чтобы драйвер имел меньше непосредственных зависимостей. Кроме того, часто имеет смысл свести к минимуму набор операций, выполняемых в функции та!и (), поместив в нее вызов драйвера, находящегося в отдельном исходном файле. Это особенно важно, если код планируется использовать в качестве библиотеки. В этом случае мы не можем использовать код в та!л 0 н должны бьп ь готовы к вызову нз различных функций (ч 9.6[8!).