Бьерн Страуструп. Язык программирования С++. Специальное издание (2011) (1004033), страница 47
Текст из файла (страница 47)
В идеале, большая часть внутреннего устройства модуля неизвестна его клиентам. Мы проводим четкое различие между устройством модуля и его интерфейсом. Например, парсер обрашается напрямую лишь к интерфейсу лексического анализатора. А лексический анализатор реализует все анонсируемые им посредством интерфейса сервисы. Это можно изобразить графически: драйвер срфейс синтаксического анализатора нтсрфсйс лексического анализатора интерфейс таблицы символов б»б ° б Пунктирные линии означают «реализует». Я считаю, что эта схема отражает реальную структуру программы, а задача программиста сводится к тому, чтобы точно отразить ее в коде.
В этом случае код будет простым, эффективным, понятным, удобным для сопровождения и т.д., потому что он напрямую соответствует фунламентальным основам нашего проекта. 221 8.2. Пространства имен Последующие разделы данной главы показывают, как сделать простой и понятной логическую структуру программы калькулятора, а в 89.3 рассматривается вопрос, как физически организовать исходный текст программы, чтобы он наилучшим образом соответствовал этой структуре.
Калькулятор — это крошечная программа, для которой в реальных условиях я не стал бы столь интенсивно использовать пространства имен и раздельную компиляцию (52.4.1, 89.1). Все это используется для демонстрации приемов, позволяющих писать большие программы так, чтобы потом не утонуть в них. В реальных программах каждый модуль, представленный отдельным пространством имен, часто содержит сотни функций, классов, шаблонов и т.п. Ради демонстрации различных практических приемов и языковых средств я намеренно провожу разбиение программы калькулятора на модули в несколько этапов.
В «реальной жизни» программы вряд ли разрабатываются в такой последовательности. Опытный программист может сразу начать с почти готового проектного решения. Тем не менее, в процессе многолетней эксплуатации программы и ее модернизации не исключена и кардинальная переделка ее структуры. Обработка ошибок проходит сквозной линией через всю структуру программы. Разбивая программу на модули, или (наоборот) собирая ее из модулей, мы должны заботиться о минимизации зависимости между модулями, проистекающей из обработки ошибок. В языке С++ обнаружение ошибок (сообщение о них) и их обработку можно четко разделить с помощью механизма исключений.
Поэтому рассмотрение пространств имен в качестве модулей (88.2) дополняется рассмотрением исс ключений, помогающим еше более улучшить модульность программы (58.3). В данной и следующей главах рассматривается лишь небольшая часть из всего множества вопросов, связанных с модульностью программ. Некоторые аспекты модульности можно было бы дополнительно рассмотреть на примере параллельно работающих и взаимодействующих между собой процессов. Модульность можно рассматривать и в смысле раздельных адресных пространств, между которыми осуществляется передача данных.
Все эти аспекты модульности довольно независимы и ортогональны друг другу. Самое интересное, что разбиение на модули почти всегда выполняется довольно просто. Намного сложнее — обеспечить безопасное, удобное и эффективное взаимодействие модулей между собой. 8.2. Пространства имен Пространства имен являются механизмом логического группирования программных объектов. Если некоторые объявления согласно определенному критерию логически близки друг другу, то для отражения этого факта их можно поместить в одно и то же пространство имен. Например, все объявления из программы калькулятора (86.1.1), относящиеся к синтаксическому анализатору (парсеру), можно поместить в одно пространство имен Рагаег: иатезрасе Рагзег ( ИоиЬ)е ехрг (Ьоо!); ИоиЬ(е рг(т (Ьоо( яей ( у * ...
* у ) г(оиЫе <егт (Ьоо! 8ет) ( 2* ... *21 г)оиЬ(а ехрг(Ьоогяей (2* ... *!) Глава 8. Пространства имен и исключения Функция ехрг() должна быть сначала объявлена, а определена потом из-за необходимости как-то разорвать порочный круг зависимостей, рассмотренный 5б.!.1.
Часть программы калькулятора, отвечаюшая за обработку ввода, также может быть помещена в свое собственное пространство имен: патеерасе Еехег ( епие Тоаеп ча(ие ( МАМЕ, ХИМЗЕК, И.11Е= ' + ', МВ'Ы= ' - ', РИЬ1Т= '," ', АЕЕ10Л'= ' = ', )' Е)))В, МИ; — '*', ЕР=' ( ', Ш'гг=' ! ', 11Р=' ) ' Тоаеп ча)ие сигг гоа; аоиЫе питаег чагие; егггпя с!ггпу ча1ие( Тоаеп чо(ее яет голеj () ( /* ... */ ) ) Применение пространств имен наглядно показывает, что именно лексический и синтаксический анализаторы предлагают своим пользователям. Однако если бы я включил в них полные определения функций, то смысл пространств имен уже не проступал бы столь отчетливо.
Когда в пространства имен реального размера включают тела функций, приходится просматривать множество страниц кода (или пролистывать множество экранов монитора), чтобы узнать, какие же собственно предоставляются сервисы, то есть каков интерфейс модуля. Альтернативой раздельного определения интерфейсов является применение программных средств„автоматически извлекающих интерфейсы из модуля реализации.
Я не считаю это хорошим решением, потому что: определение интерфейсов является существенной частью процесса проектирования (523.4.3.4), модуль может предоставлять разные интерфейсы разным пользователям, и к тому же разработка интерфейсов часто ведется задолго до конкретизации деталей реализации. Вот новый вариант пространства имен Рагеег, в котором интерфейс явным образом отделен от реализации: патеерасе Рагеег ( ФоиЫе рпе (Ьоо!); ЫоиЫе геггп (Ьоо1); ИоиЫе ехрг (Ьоог); ) г(оиЫе Ратег::рат (Ьоо1 ее!) ( l* ... *l ) йоиЫе Рагеег::тест (Ьоо! яег) (l* ...
*l ) йоиЫе Рагзег::ехрг(Ьоо1 еег) (/* ... *l) Обратите внимание на то, что теперь каждая функция имеет ровно одно объявление, и одно определение. Пользователям нужно ознакомиться с интерфейсом, содержашим лишь объявления. А реализация может быть помешена в какое-нибудь дру~ое место, куда пользователю заглядывать нет необходимости. 223 8.2. Пространства имен Как продемонстрировано в последнем примере, элемент (тетЬег-пате) пространства имен (патезрасе-пате) может быть в нем лишь объявлен, а определен позднее в нотации патезрасе-пате:: тетЬег-пате. Члены (элементы) пространства имен объявляются следующим образом: патезрасе патезрасе-пате ( УУ объявления и определения ) Нельзя обьявлять новый элемент пространства имен вне этого определения (не поможет и явная квалификация имени): гоЫ Рагзег::1оя(со! )Ьоо!) ! УУеггою нет никакого!о8!са!() в Рагзег Идея состоит в том, чтобы все элементы пространства имен легко обнаруживались в одном определении, и чтобы можно было легко отлавливать ошибки, связанные, например, с описками или несоответствием типов.
Например: доиЫе Ратег::ггет )Ьоо1) ! УУеггог нет никакого ггет() в Рагзег доиЫе Рагзег::рпт (!п!) ! У еггог: Рагзег::рмтб принимает аргумент Ьоо! 8.2.1. Квалифицированные имена Пространство имен является отдельной областью видимости. Обычнь!е правила для областей видимости распространяются и на пространства имен, так что если имя объявлено ранее в том же самом пространстве имен (или в охватываюшей области видимости), то его можно использовать без проблем.
Имена же из других пространств имен требуют дополнительной квалификации. Например: У нужна квалификация Рагзег:: УУ нет нужды в квалификации доиЫе Рагзег:: !ест ! Ьоо! 8е!) ( доиЫе 1етт = рлт (яе!); уог(г;) знвгсЬ (!.ехег::сиге гоЬ) сазе Аехег:: МШ.: УУ нужна квалификация !ехег: У нужна квалификация (ехег: Пространство имен формирует свою собственную область видимости.
Таким образом, пространство имен является одновременно и фундаментальной, и достаточно простой концепцией. Чем больше размер программы, тем большую пользу приносят пространства имен, помогая четко разделять программу на логические части. Обычные локальные и глобальные области видимости, а также классы, формируют свои пространства имен (8С.)0.3).
В идеале, каждая программная сущность должна принадлежать некоторой четко ограниченной логической единице («модулю»), Поэтому любое объявление в нетривиальной программе должно в идеале помещаться в пространство имен, собственное имя которого должно отражать его логическую роль в программе. Исключение составляет функция таза(), которая должна оставаться глобальной, чтобы исполнительная система всегда могла отыскать стартовую функцию (88.3.3). 224 Глава 8. Пространства имен и исключения 1еу(*=р«1т ((«ие) 1 уу ... ) УУ ... ) УУ нет нужды в квалифика((ии Квалификатор Рагзег указывает, что функция (егт () объявлена именно в пространстве имен Рагзег, а не где-то еше (например, в глобальной области видимости). Так как 1егт () является членом Рагзег, нет необходимости квалифицировать иной член этого же пространства имен — р«1т () . А вот если убрать квалификатор Еехег, то имя сиге (оа будет считаться необъявленным, поскольку имена из пространства имен Еехег не входят в область видимости пространства имен Рагзег.
8.2.2. Объявления (зв1лд ((оиЫе Рагзег::рпт (Ьоо( ее() УУ обработка первичных выразкений ( (((ее() Еехег::ее( (ойеп () 1 знвсЬ (Еехег::сигг (ой) сазе Еехег:: )((ЬМВЕВ ( Еехег:: ее( (ойеп () 1 ге(игп Еехег:: питЬег га(ие( сазе Еехег:: 1ЧАМЕ: ( ((оиЫез г=(аЫе(Еехег::зггтк га!ие); (((Еахег::Ве( (ойеп() ==Еехе«: (АКЯБ«() г=ехрг((гие) 1 ге(игп г( сазе Еехег:: МЕ!Ч'ыб: УУ унарный минус ге(игп -рг(т (1«ие) 1 сазе Еехег:: ЕР: ( ((оиЫе е = ехрг (О ие) 1 (1 (Еехег( (сиге (ой! = Еехег::ЮР) ге(игп Еггог::еггог(" Еехег( (яе( (ойеп () 1 В пропустить скобку 1' ге(игп е( ) сазе Еехег:: ЕХР ( гетгп 11 ((еГаи(1 ( ГЕ1и«П Ее«О«:: ЕГГОГ ("ритаГу ЕХрЕС(Еат ) 1 ) УУ константа с плавающей запятой ) ехрес(е((ч ) Назойливое повторение квалификатора Еехег утомительно и отвлекает внимание.
Такую избыточность можно устранить с помощью объявления из(пя (из(пя-Нес!а- Когда имя часто используется вне пределов своего пространства имен, может быть утомительно то и дело дополнительно квалифицировать его. Рассмотрим следуюший пример: 8.2. Пространства имен 225 галоп), которое позволяет однократно указать для текущей области видимости, что имя яег гойеи взято из пространства имен Еехег. Например: йоиЫе Ратзег::рпт (Ьоо) Веи //обработка первичных выражений ( из/па Еехег::вег годен; //использоват~ ае( (слеп из Еехег аяша Еехег::ситг той," // использовать сип (ок из Еехет ия/па Епог::етгог; // использовать етгог из Еггог (1(аее) аег тайен (); знйсй (сип год) ( сазе Еехег:: Р/1/МВЕК: аез (ойеп (); тегиги Еехег:: питбег та/ие) сазе Еехет:: /чАМЕ: ( аоиЫез т = заЫе(Еехег::язт/пи гагие]; ьу(Вез гойеп () == Еехег::АЯ516/ч) г = ехрг(ние) з тезиса тз ) сазе Еехет:: М1г/бБ: //упорный минус гегигп -рпт (и ив); сазе Еехег:: ЕР: ( аоиЫе е=ехрг (згие); з1 (ситг Гой ! = Еехег::ИР) ге(итп егтог(") ехресгеат) аез гойеп (); //пропустить скобку 'Р гегигп е; саяе Еехег:: Ер/ЕУ: тегиги 1; вега и 1(ч Гвзитн ЕГГОГ ("рнтату ЕХрвегваы ); // константа с плавающей запятой пазиезрасе Рагзег ( аоиЫе рпт (Ьоо1); ФоиЫе гегт (Ьоог); аоиЫе ехрг (Ьоо1); ия/па Еехег:: аез Голеи; изгои Еехег:: сигг гойю ия(па Еггог:: епог; //использовать ке( (олен из Еехет // использовать сигг (ол из Еехег // использовать еггог из Епог Объявление из1ии вводит локальный синоним.