Урок 23. Header guards

   ⁄ 

 Обновлено 17 Апр 2017

  ⁄   

В уроке о предварительных объявлениях мы усвоили, что идентификатор может иметь только одно определение. Таким образом, программа, в которой есть больше одного определения одной и той же переменной, получит ошибку компиляции:

Это также относится и к программам, в которых одни и те же функции определены больше одного раза:

Хотя эти программы легко исправить (достаточно удалить строчки с дублированием), с заголовочными файлами дело может выйти немного щекотливее. Довольно легко попасть в ситуацию, когда определения, которые находятся в одном заголовке, будут подключены больше одного раза в определенный .cpp файл. Подобное характерно, когда один заголовок #include другой заголовок.

Рассмотрим следующий пример:

math.h:

geometry.h:

main.cpp:

Эта, казалось бы, невинная программа, не скомпилируется! Проблема кроется в определении функции в файле math.h. Давайте детальнее рассмотрим, что на самом деле происходит. Во-первых, main.cpp #include «math.h», в котором определение функции getSquareSides копируется в main.cpp. Затем main.cpp #include «geometry.h», который соответственно #include «math.h». В geometry.h находится копия функции getSquareSides с math.h, которая уже второй раз скопируется в main.cpp.

Таким образом, после выполнения всех директив #include, main.cpp будет с вот таким кодом:

Дублирование определений и, вследствии этого, ошибка компиляции. Если смотреть каждый файл по отдельности, то они будут без ошибок. Однако, в main.cpp, который #include два заголовочных файла с одним и тем же определением, мы столкнемся с проблемами. Если для geometry.h нужна функция getSquareSides, а для main.cpp нужен как geometry.h, так и math.h, то какое же решение?

Header guards

На самом деле решение простое – header guards (“охранники заголовков”, другое название — include guards). Header guards — это директивы условной компиляции, которые состоят из следующего:

Если подключить этот заголовочный файл, то первое, что он сделает — это проверит, был ли ранее определен SOME_UNIQUE_NAME_HERE. Если мы впервые подключаем этот заголовок, SOME_UNIQUE_NAME_HERE еще не был ни разу определен. Следовательно, мы #define (определяем) SOME_UNIQUE_NAME_HERE и пишем основной код заголовочного файла. Если мы раньше уже подключали (#include) этот заголовок, то SOME_UNIQUE_NAME_HERE уже определен. Таким образом, при подключении заголовка второй раз, весь заголовок будет игнорироваться.

Все ваши заголовочные файлы должны быть с header guards. SOME_UNIQUE_NAME_HERE может быть любым идентификатором, которое вы только захотите, но, как правило, в качестве идентификатора используется имя заголовка с приставкой _H. Например, в math.h header guard будет MATH_H:

math.h:

Даже заголовочные файлы стандартной библиотеки используют header guards. Если бы вы взглянули на заголовок iostream из Visual Studio, вы бы увидели:

Вернемся к примеру с math.h, но уже с использованием header guards.

math.h

geometry.h:

main.cpp:

Теперь, когда main.cpp #include «math.h», препроцессор будет видеть, что MATH_H еще не был определен. Содержание math.h копируется в main.cpp и MATH_H определяется. Затем main.cpp #include «geometry.h», который #include «math.h «. Препроцессор видит, что MATH_H уже был ранее определен и содержимое между header guards игнорируется.

Таким образом, используя header guards, мы предотвратили дублирование определений.

#pragma once

Большинство компиляторов поддерживают более простую, альтернативную форму header guards — директиву #pragma:

#pragma once используется, как и header guards, но имеет дополнительные преимущества – она короче и менее подвержена ошибкам. Файл stdafx.h, который мы постоянно подключаем в Visual Studio в проектах, использует именно эту директиву вместо header guards.

Однако, #pragma once не является официальной частью языка C++, и не все компиляторы имеют её поддержку (хотя большинство современных компиляторов поддерживают).

В целях полной совместимости, мы рекомендуем использовать header guards.

Выводы

Header guards предотвращают копирование содержимого заголовочного файла больше одного раза в какой-либо один файл, в целях избежания дублирования определений.

Обратите внимание, что дублирование ОБЪЯВЛЕНИЙ (не определений) не вызывают те же проблемы, что с определениями, так как объявление может быть объявлено несколько раз без инцидентов, но даже если ваш заголовочный файл состоит из одних только объявлений, по-прежнему считается хорошим тоном использовать header guards.

Тест

1) Используя приведенный выше пример с math.h, добавьте header guards в файл geometry.h

Ответ

Чтобы увидеть результат, нажмите на ссылку.

Ответ 1

Оценить статью:

Звёзд: 1Звёзд: 2Звёзд: 3Звёзд: 4Звёзд: 5 (22 оценок, среднее: 4,64 из 5)
Загрузка...
Поделиться в:
Подписаться на обновления:

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *