Защита от повторного включения
Вернёмся в контору. Одна памятка иногда ссылается на другую: «к этой странице приложи копию config.h». Если отдел заказал обе памятки, секретарь вложит config.h в папку дважды — один раз напрямую, второй раз вместе с той памяткой, что на неё ссылается.
С заголовками происходит то же. Заголовок config.h определяет константу constexpr int kMaxUsers = 100;. Заголовок users.h подключает config.h, а main.cpp подключает оба — и не компилируется:
#include "config.h"
#include "users.h" // ✗
int main() {
return kMaxUsers;
}error: redefinition of 'kMaxUsers'Откуда второе определение
Препроцессор вставляет текст и ничего в нём не проверяет. В main.cpp текст config.h попадает дважды: напрямую и через users.h. В единице трансляции оказываются два определения kMaxUsers, и компилятор их не принимает. Объявление функции можно повторить сколько угодно раз, а определение — нет.
Вставляйте #include по одному и следите, сколько раз текст config.h попадает в main.cpp, а потом включите защиту.
constexpr int kMaxUsers = 100;#include "config.h" int count_users();
#include "config.h"#include "users.h" int main() { return kMaxUsers;}
#include "config.h" int report() { return kMaxUsers;}
#include "config.h"ждёт#include "users.h"ждёт- int main() {
- return kMaxUsers;
- }
#include "config.h"ждёт- int report() {
- return kMaxUsers;
- }
Препроцессор идёт по строкам #include сверху вниз и вставляет вместо каждой текст файла. Нажмите кнопку.
Две формы защиты
Защита от повторного включения не даёт тексту заголовка попасть в единицу трансляции дважды. В конторе это отметка на папке: «config.h уже вложен». У защиты две формы.
#pragma once
Короче всего защиту записать одной строкой в начале заголовка:
#pragma once
constexpr int kMaxUsers = 100;Когда препроцессор второй раз встречает #include этого файла в той же единице трансляции, он пропускает файл. #pragma once не входит в стандарт C++, но его понимают GCC, Clang и MSVC.
#ifndef, #define и #endif
Стандартная форма — три директивы:
#ifndef CONFIG_Hв начале файла читается как «если макросCONFIG_Hне определён»;#define CONFIG_Hсразу за ней;#endifв конце файла.
#ifndef CONFIG_H
#define CONFIG_H
constexpr int kMaxUsers = 100;
#endifМакрос CONFIG_H создают без текста: он нужен только как отметка «заголовок уже вставлен». Препроцессор проходит этот файл так:
- При первом включении
CONFIG_Hещё не определён:#ifndefпропускает текст дальше, и#defineопределяет макрос. - При втором включении
CONFIG_Hуже определён, и#ifndefпропускает всё до#endif.
Имя макроса берут из имени файла, заглавными буквами, чтобы у разных заголовков оно не совпало.
Одна единица трансляции
Защита действует внутри одной единицы трансляции. Каждый другой .cpp-файл, который подключает заголовок, получает свою копию его текста. Отметка стоит на одной папке, а в других папках её нет.
Частая ошибка
Заголовок users.h создали копией config.h и забыли поменять имя макроса:
#ifndef CONFIG_H // ✗
#define CONFIG_H
int count_users();
#endifФайл main.cpp подключает сначала config.h, потом users.h и вызывает count_users(). Когда препроцессор доходит до users.h, макрос CONFIG_H уже определён, и весь текст users.h пропадает. Компилятор не находит объявления:
error: use of undeclared identifier 'count_users'Дайте каждому заголовку свой макрос: USERS_H для users.h.
Главное
#includeвставляет текст слово в слово, поэтому заголовок может попасть в единицу трансляции дважды.- Второе определение в одной единице трансляции компилятор не принимает.
#pragma onceили тройка#ifndef,#define,#endifпропускают повторную вставку.- Защита действует внутри одной единицы трансляции, а не на всю программу.
Подпишись, чтобы пройти курс
Курс бесплатный. Войди через Telegram и подпишись на канал — откроются все уроки.