8.2.3. Несколько слов о заголовочных файлах
8.2.3. Несколько слов о заголовочных файлах
Заголовочный файл предоставляет место для всех extern-объявлений объектов, объявлений функций и определений встроенных функций. Это называется локализацией объявлений. Те исходные файлы, где объект или функция определяется или используется, должны включать заголовочный файл.
Такие файлы позволяют добиться двух целей. Во-первых, гарантируется, что все исходные файлы содержат одно и то же объявление для глобального объекта или функции. Во-вторых, при необходимости изменить объявление это изменение делается в одном месте, что исключает возможность забыть внести правку в какой-то из исходных файлов.
Пример с addToken() имеет следующий заголовочный файл:
// ----- token.h -----
typedef unsigned char uchar;
const uchar INLINE = 128;
// ...
const uchar IT = ...;
const uchar GT = ...;
extern uchar lastTok;
extern int addToken( uchar );
inline bool is_relational( uchar tok )
{ return (tok = LT tok = GT); }
// ----- lex.C -----
#include "token.h"
// ...
// ----- token.C -----
#include "token.h"
// ...
При проектировании заголовочных файлов нужно учитывать несколько моментов. Все объявления такого файла должны быть логически связанными. Если он слишком велик или содержит слишком много не связанных друг с другом элементов, программисты не станут включать его, экономя на времени компиляции. Для уменьшения временных затрат в некоторых реализациях С++ предусматривается использование предкомпилированных заголовочных файлов. В руководстве к компилятору сказано, как создать такой файл из обычного. Если в вашей программе используются большие заголовочные файлы, применение предкомпиляции может значительно сократить время обработки.
Чтобы это стало возможным, заголовочный файл не должен содержать объявлений встроенных (inline) функций и объектов. Любая из следующих инструкций является определением и, следовательно, не может быть использована в заголовочном файле:
extern int ival = 10;
double fica_rate;
extern void dummy () {}
Хотя переменная i объявлена с ключевым словом extern, явная инициализация превращает ее объявление в определение. Точно так же и функция dummy(), несмотря на явное объявление как extern, определяется здесь же: пустые фигурные скобки содержат ее тело. Переменная fica_rate определяется и без явной инициализации: об этом говорит отсутствие ключевого слова extern. Включение такого заголовочного файла в два или более исходных файла одной программы вызовет ошибку связывания – повторные определения объектов.
В файле token.h, приведенном выше, константа INLINE и встроенная функция is_relational() кажутся нарушающими правило. Однако это не так.
Определения символических констант и встроенных функций являются специальными видами определений: те и другие могут появиться в программе несколько раз.
При возможности компилятор заменяет имя символической константы ее значением. Этот процесс называют подстановкой константы. Например, компилятор подставит 128 вместо INLINE везде, где это имя встретится в исходном файле. Для того чтобы компилятор произвел такую замену, определение константы (значение, которым она инициализирована) должно быть видимо в том месте, где она используется. Определение символической константы может появиться несколько раз в разных файлах, потому что в результирующем исполняемом файле благодаря подстановке оно будет только одно.
В некоторых случаях, однако, такая подстановка невозможна. Тогда лучше вынести инициализацию константы в отдельный исходный файл. Это делается с помощью явного объявления константы как extern. Например:
// ----- заголовочный файл -----
const int buf_chunk = 1024;
extern char *const bufp;
// ----- исходный файл -----
char *const bufp = new char[buf_chunk];
Хотя bufp объявлена как const, ее значение не может быть вычислено во время компиляции (она инициализируется с помощью оператора new, который требует вызова библиотечной функции). Такая конструкция в заголовочном файле означала бы, что константа определяется каждый раз, когда этот заголовочный файл включается. Символическая константа – это любой объект, объявленный со спецификатором const. Можете ли вы сказать, почему следующее объявление, помещенное в заголовочный файл, вызывает ошибку связывания, если такой файл включается в два различных исходных?
// ошибка: не должно быть в заголовочном файле
const char* msg = "?? oops: error: ";
Проблема вызвана тем, что msg не константа. Это неконстантный указатель, адресующий константу. Правильное объявление выглядит так (полное описание объявлений указателей см. в главе 3):
const char *const msg = "?? oops: error: ";
Такое определение может появиться в разных файлах.
Схожая ситуация наблюдается и со встроенными функциями. Для того чтобы компилятор мог подставить тело функции “по месту”, он должен видеть ее определение. (Встроенные функции были представлены в разделе 7.6.)
Следовательно, встроенная функция, необходимая в нескольких исходных файлах, должна быть определена в заголовочном файле. Однако спецификация inline – только “совет” компилятору. Будет ли функция встроенной везде или только в данном конкретном месте, зависит от множества обстоятельств. Если компилятор пренебрегает спецификацией inline, он генерирует определение функции в исполняемом файле. Если такое определение появится в данном файле больше одного раза, это будет означать ненужную трату памяти.
Большинство компиляторов выдают предупреждение в любом из следующих случаев (обычно это требует включения режима выдачи предупреждений):
* само определение функции не позволяет встроить ее. Например, она слишком сложна. В таком случае попробуйте переписать функцию или уберите спецификацию inline и поместите определение функции в исходный файл;
* конкретный вызов функции может не быть “подставлен по месту”. Например, в оригинальной реализации С++ компании ATT (cfront) такая подстановка невозможна для второго вызова в пределах одного и того же выражения. В такой ситуации выражение следует переписать, разделив вызовы встроенных функций.
Перед тем как употребить спецификацию inline, изучите поведение функции во время выполнения. Убедитесь, что ее действительно можно встроить. Мы не рекомендуем объявлять функции встроенными и помещать их определения в заголовочный файл, если они не могут быть таковыми по своей природе.
Упражнение 8.3
Установите, какие из приведенных ниже инструкций являются объявлениями, а какие – определениями, и почему:
(a) extern int ix = 1024;
(b) int iy;
(c) extern void reset( void *p ) { /* ... */ }
(d) extern const int *pi;
(e) void print( const matrix );
Упражнение 8.4
Какие из приведенных ниже объявлений и определений вы поместили бы в заголовочный файл? В исходный файл? Почему?
(a) int var;
(b) inline bool is_equal( const SmallInt , const SmallInt ){ }
(c) void putValues( int *arr, int size );
(d) const double pi = 3.1416;
(e) extern int total = 255;
Более 800 000 книг и аудиокниг! 📚
Получи 2 месяца Литрес Подписки в подарок и наслаждайся неограниченным чтением
ПОЛУЧИТЬ ПОДАРОКЧитайте также
И несколько последних слов
И несколько последних слов Все те концептуальные особенности Zenwalk»а, описанные выше, показались части его разработчиков не совсем соответствующими духу первозданного Linux»а. И в результате от него отделился проект Salix. Но о нём сейчас разговора не будет – он выпадает и за
Несколько слов в заключение
Несколько слов в заключение Прочитав эту книгу, вам наверняка захочется узнать больше о выбранном вами дистрибутиве - Fedora8. Поэтому здесь мы поговорим об источниках информации по Linux.Linux - это отлично документированная система. В составе любого дистрибутива есть довольно
Еще несколько слов о языке
Еще несколько слов о языке Перед тем как закончить наш разговор о коммуникации, неплохо было бы затронуть тему грамматических ошибок. Проблемы с правописанием встречаются в Сети довольно часто и вызывают у посетителей не самые лучшие
59. Не используйте using для пространств имен в заголовочных файлах или перед директивой #include
59. Не используйте using для пространств имен в заголовочных файлах или перед директивой #include РезюмеДиректива using для пространств имен создана для вашего удобства, а не для головной боли других. Никогда не используйте объявления или директивы using перед директивой #include.Вывод:
14.8.1. Несколько слов о текстовых фильтрах
14.8.1. Несколько слов о текстовых фильтрах Многие инструменты, которыми мы постоянно пользуемся (как поставляемые производителем, так и разрабатываемые собственными силами), — просто текстовые фильтры. Иными словами, они принимают на входе текст, каким-то образом
Несколько слов о вложенных делегатах
Несколько слов о вложенных делегатах Завершим эту главу рассмотрением еще одного аспекта обобщенных делегатов. Вы знаете, что делегаты могут быть вложены в тип класса, что должно означать тесную ассоциацию между этими двумя ссылочными типами. Если тип-контейнер при этом
Несколько слов о System.CodeDOM
Несколько слов о System.CodeDOM Теперь, когда мы с вами выяснили, как создаются динамические компоновочные блоки с помощью System.Reflection.Emit и различных лексем CIL, я должен сообщить вам, что есть и другая (часто более простая) альтернатива. Платформа .NET предлагает технологию под
Несколько слов о IpcChannel
Несколько слов о IpcChannel Перед тем как перейти к обсуждению файлов конфигурации удаленного взаимодействия, напомним, что .NET 2.0 предлагает тип IpcChannel, обеспечивающий самый быстрый из возможных способов взаимодействия приложений на одной машине. Задачей данной главы
Несколько слов о System.Web.UI.HtmlControls
Несколько слов о System.Web.UI.HtmlControls Вообще говоря, есть два разных набора Web-элементов управления, предлагаемых в рамках дистрибутива .NET 2.0. В дополнение к Web-элементам управления ASP.NET (из пространства имен System.Web.UI.WebControls), библиотеки базовых классов предлагают также элементы
Несколько слов о данных состояния элементов
Несколько слов о данных состояния элементов В ASP.NET 2.0 предлагается поддержка состояний элементов управления, а не только состояния представлений. Эта технология оказывается очень удобной при работе с созданными вами Web-элементами управления ASP.NET, которые должны
Несколько слов о связи
Несколько слов о связи Несомненно, маленькие мобильные устройства, будь то смартфон или КПК, идеально подходят на роль коммуникационных устройств. В этой главе были приведены только самые простые примеры использования связи между устройствами. В последнее время
1.1. Несколько вступительных слов об Ubuntu
1.1. Несколько вступительных слов об Ubuntu Как ни прискорбно сознавать, но если тенденции развития Linux не изменятся, то скоро она станет такой же коммерческой операционной системой, как и Windows. Уже сегодня цена некоторых дистрибутивов (тот же Mandriva PowerPack) в ряде
19.1. Несколько слов о GRUB2
19.1. Несколько слов о GRUB2 Загрузчик GRUB (GRand Unified Bootloader) считается более гибким и современным, чем LILO (Linux Loader). Благодаря иной схеме загрузки операционных систем GRUB понимает больше файловых систем, нежели LILO, а именно: FAT/FAT32, ext2, ext3, ReiserFS, XFS, BSDFS и др.Но время не стоит на месте. В
Несколько слов об индексировании компонентов
Несколько слов об индексировании компонентов На стыке технических и организационных проблем возникает вопрос: как следует связывать индексирующую информацию, например ключевые слова с программными компонентами? Принцип Самодокументирования говорит о том, что вся
§ 1.3 Несколько слов о XML
§ 1.3 Несколько слов о XML Расширяемый язык разметки — eXtensible Markup Language, был создан для хранения структурированных данных в текстовом формате. Теоретически файлы XML должны легко читаться, как программным обеспечением, так и человеком.С использованием технологии XML можно