#include, #define и защита от повторного включения
Заголовок 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'Препроцессор
До компилятора файл читает препроцессор (preprocessor) и выполняет директивы — строки, которые начинаются с #. Директива #include заменяет себя текстом указанного файла, слово в слово, ничего в нём не проверяя. В main.cpp текст config.h попадает дважды: напрямую и через users.h. В единице трансляции оказываются два определения kMaxUsers, и компилятор их не принимает.
Макросы
Директива #define создаёт макрос (macro) — имя, которое препроцессор заменяет заданным текстом. Макрос может обозначать константу, а может принимать параметры, как функция:
#include <iostream>
#define MAX_USERS 100
#define SQUARE(x) x * x
int main() {
std::cout << MAX_USERS << ' ' << SQUARE(7) << '\n'; // 100 49
}Компилятор получает уже 100 и 7 * 7. Аргумент (argument) макроса препроцессор не вычисляет: он вставляет его текст на место x как есть и скобок не добавляет. Получившееся выражение компилятор считает по обычному приоритету операторов.
Имена макросов пишут заглавными буквами, чтобы их не путали с переменными и функциями. Для констант вместо макросов используют constexpr-переменные: у них есть тип, и их проверяет компилятор.
Защита от повторного включения
Защита от повторного включения (include guard) не даёт тексту заголовка попасть в единицу трансляции дважды. Короче всего её записать строкой #pragma once в начале заголовка:
#pragma once
constexpr int kMaxUsers = 100;Когда #include встречает этот файл в той же единице трансляции второй раз, препроцессор его пропускает. #pragma once не входит в стандарт C++, но его понимают GCC, Clang и MSVC. Стандартная форма — три директивы: #ifndef CONFIG_H и #define CONFIG_H в начале файла и #endif в конце. При первом включении заголовка макрос CONFIG_H ещё не определён, и текст проходит, а при втором #ifndef пропускает всё до #endif.
Защита действует внутри одной единицы трансляции. Каждый другой .cpp-файл, который подключает заголовок, получает свою копию его текста.
Частая ошибка
Макрос делит аргумент пополам, но результат неверный:
#include <iostream>
#define HALF(x) x / 2
int main() {
int result = HALF(10 + 4); // ✗
std::cout << result << '\n'; // 12
}Ни компилятор, ни программа об ошибке не сообщают. Препроцессор превратил HALF(10 + 4) в 10 + 4 / 2, а деление выполняется раньше сложения.
Вместо такого макроса напишите функцию: constexpr int half(int x) { return x / 2; }. Функция получает аргумент уже вычисленным — 14. С constexpr вызов функции с константным аргументом может инициализировать и constexpr-переменную: такой вызов компилятор обязан вычислить при компиляции.