Б. Страуструп - Дизайн и Эволюция C++. 2006 (1160775), страница 27
Текст из файла (страница 27)
)з> содержит примерно такой код: с(авв Ео ооопсес всасхс Епс соцпс; рпЫЕс: Ео соцпсес() ( ЕЕ (соцпс++ == О) ( /* инициализировать с1п, соцс и т.д. "/ ) -Ео соцпсес() ( 1Е (--соипс == О) ( /* очистить с1п, попс и т.д. */ всасхс Ео соппсег Ео 1п1с/ Теперь каждый файл, включающий заголовок <1овсгеав.
)з>, создает и инициализирует также объект 1о соцпсег, в результате чего увеличивается счетчик 1о соцпсег:: соцпс. Когда это происходит в первый раз, инициализируются библиотечные объекты. Поскольку заголовок предшествует любому использованию библиотечных средств, правильная инициализация гарантируется. Так как деструкторы вызываются в порядке, обратном вызову конструкторов, этот прием гарантирует также корректную очистку вслед за последним использованием библиотеки. Таким образом решается проблема зависимости от порядка, причем от поставщика библиотеки требуется добавить лишь несколько почти стандартных строк кода.
К сожалению, последствия для производительности бывают весьма серьезными. При использовании таких трюков код динамической инициализации будет присутствовать в большинстве объектных файлов, а значит (если предположить, что применялся обычный компоновщик), вызовы функций инициализации будут разбросаны по всему адресному пространству процесса. В системах с виртуальной памятью это означает, что на этапе начальной инициализации и на этапе Второстепенные возможности ЯИИИИИИИ финальной очистки в основную память подгрузится большая часть страниц программы. Такое использование виртуальной памяти нельзя признать разумным, ибо при запуске объемных приложений будет наблюдаться заметная (порядка нескольких секунд) задержка.
Для разработчика инструментальных средств тривиальное решение — модифицировать компоновщик так, чтобы он собирал весь код, выполняемый при инициализации, в одном месте. Сложностей не возникает и в случае, когда система ни в каком виде не поддерживает частичную динамическую загрузку программы в основную память. Однако это слабое утешение для программиста на С++, столкнувшегося с такой проблемой [Ке!эег, 19921 Мы имеем здесь нарушение основополагающего принципа, говорящего, что любая возможность С++ должна быть не только полезной, но и не накладной (см. раздел 4.3).
Можно ли решить проблему, добавив какое-то новое средство? На первый взгляд, нет, поскольку ни дизайн языка, ни даже официальные комитеты по стандартизации не могут узаконить эффективность языка. В предложениях, которые мне довелось читать, делаются попытки решить проблему зависимости от порядка, которая уже решена приемом, предложенным Джерри, но не касаются возникающего при этом вопроса об эффективности. Подозреваю, что настоящее решение состоит в том, чтобы убедить разработчиков инструментальных средств предотвратить «избиение виртуальной памяти» (ч(ггпа! шешогу ЬаэЬ(пВ), вызванное процедурами динамической инициализации. Для этого нужно лишь вставить необходимые слова в стандарт.
3. 114З. Динамическая инициализация встроенных типов В языке С статический объект можно инициализировать лишь слегка расширенным константным выражением, например: бооЫе Рг = 22/7; /* правильно */ бооЫе вцго2 = вцго(2); /* в С это ошибка */ Однако в С++ возможны выражения общего вида при инициализации объектов, например: ПооЫе в2 = вс(гг(2); // правильно Стало быть, для встроенных типов обеспечивается меньшая поддержка, чем для классов.
Это несоответствие было окончателы|о исправлено в версии 2.0: бооЫе вцгс2 = вцгс (2) | // правильно в с«» (начиная с версии 2.0) ЗЛ1.5. Предложения обьяяления В А!Во!68 есть прекрасная идея о том, что объявление может вводиться в любом месте, а не только в начале блока. В С++ я сделал возможными идиомы «только инициализация» или «единственное присваивание», менее подверженные ошибкам, чем традиционный стиль. Такой подход абсолютно необходим для ссылок и констант, которым нельзя присваивать значения, и более эффективен для типов, инициализация которых по умолчанию — дорогая операция. Например: НИИИ ИИИ!1 Рождение С++ коЫ Е(гпг 1, сопаг сваг* р) ( [с (г<=0) еггог("отрицательный индекс"); попас [пс 1еп = асг1еп(р); ясг[пд а(р); // ) Гарантированная инициализация за счет конструкторов — это другая сторона усилий, направленных на уменьшение числа ошибок из-за переменных.
3.11.5.1. Объявления в циклах гог Одна из самых распространенных причин для введения новой переменной в середине блока — объявление переменной цикла. Например: ьпс гог (1=0; 1<МАХ; 1»») // Чтобы не разделять объявление переменной и ее инициализацию, объявление было разрешено переносить в заголовок цикла: гог ([пг г=0; 1<МЛХ; 1»») // К сожалению, я не стал менять семантику так, чтобы ограничить область действия переменной, введенной таким образом, областью действия предложения аког. Основная причина была в том, чтобы избежать специального исключения из правила, гласящего: <область действия переменной простирается от точки объявления до конца наименьшего объемлющего блока».
Это правило стало предметом многочисленных споров и, в конце концов, было пересмотрено, так чтобы соответствовать правилу для объявлений в условиях [см. раздел 3.11.5.2), Именно имя, введенное инициализатором цикла аког, выходит из области действия в конце этого предложения. 3. 11.5.2. Объявления в условиях Пользователи, добросовестно старающиеся избежать неинициализированных переменных, сталкиваются с такими примерами: а переменные, используемые для ввода: [п сгп»1; а переменные, используемые в условиях: то)< *ос; [Г (сС = деССо)<()) [ /* ... */ ) При проектировании механизма идентификации типов во время исполнения в 1991 г.
(см. раздел 14.2.2.1), я обнаружил, что вторую причину появления неинициализированных переменных можно устранить, если разрешить объявления в условиях: Языки С и С++ ЛИВИИИИШ Гг )ток* сс = пессох)) ) ( !/ здесь сс находится в области действия ) !! а здесь сз уже вышла из области действия 3.12. Языки С и С++ После появления названия С++ и написания справочного руководства по языку [8ггопз1гпр, 1984] вопросом, которому придавалось большое значение и вокруг )азторого завязалась ожесточенная полемика, стала совместимость нового языка с С.
Кроме того, в начале 1983 г. подразделение Вей ЕаЬз, занимавшееся разработкой и поддержкой ПЫ1Х и выпускавшее серию компьютеров АТйТ ЗВ, заинтересовалось С++ настолько, что решило выделить ресурсы под разработку инструментальных средств для него. Наличие инструментария было необходимо для того, чтобы С++ стал языком, который мог использоваться компанией для создания важных проектов. К сожалению, это означало также, что С++ попадает в поле зрения административных структур, отвечающих за разработку. Сначала руководство потребовало обеспечить стопроцентную совместимость языка с С.
Вообще совместимость — это вполне разумно, но практика программирования не так проста. Во-первых, с каким именно С должен быть совместим С++? У С было множество диалектов, и, хотя на горизонте вырисовывался АК81 С, до появления стабильного определения должны были пройти годы, причем диалекты все равно до конца не устранялись. Помню, я тогда подсчитал — в шутку, но ведь в каждой шутке есть доля правды, — что может существовать примерно Это не просто трюк с целью уменьшить число вводимых символов, а функционирование в точном соответствии с правилами локального объявления переменных. Объединив в одном предложении объявление переменной, ее инициализацию и проверку результата инициализации, мы добиваемся компактности, позволяющей устранить ошибку, которая может возникнуть вследствие использования неинициализированной переменной.
Ограничив область действия такой переменной предложением, управляемым условием, мы также решили проблему случайного «повторного использованияз переменных, которые больше не нужны. На мысль разрешить объявления в выражениях меня натолкнуло изучение языков на базе выражений, прежде всего А!Во!68. Так, в А18о!68 объявление порождало значение, и этот принцип я положил в основу своего дизайна. Позже стало понятно, что память подвела меня: как раз обьявления были одной из немногих конструкций в А18о168, которые значений не порождали! Вот что сказал по этому поводу Чарльз Линдсей: «Даже в А18о168 есть несколько изъянов, которые говорят о том, что этот язык не полностью непротиворечивм Если бы я проектировал язык с нуля, то пошел бы по пути А!Во!68 и сделал бы каждое предложение и объявление выражением, имеющим значение.
Возможно, неинициализированные переменные были бы полностью запрещены и не разрешалось бы указывать более одного имени в одном объявлении. Однако эти идеи, очевидно, неприемлемы для С++. ПИИИИ>611 Рождение С++ 3" диалектов АХЯ1 С. При подсчете учитывалось число неопределенных и зависящих от реализации аспектов, а 3 было выбрано в качестве основания как среднее число вариантов каждого такого аспекта. Естественно, средний пользователь, жаждущий совместимости с С, понимал под ней совместимость с локальным диалектом. Это виделось важной практической проблемой, которой я и мои друзья уделяли много внимания. Администраторам и продавцам до нее было меньше дела: они либо не вполне понимали технические детали, либо их интерес к С++ объяснялся желанием привязать пользователей к своим программным и аппаратным решениям. С другой стороны, разработчики С++ в Ве1! 1.аЪз независимо от того, в каком подразделении они работали, были «эмоционально преданы концепции переносимости» [)ойпзоп, 1992] и противились требованиям администрации закрепить конкретный диалект С в определении С++.