Зачем нужны директивы препроцессора в C++
Что проверяют
Понимаете ли вы, что препроцессор работает до компиляции и ничего не знает о типах и областях видимости, и отличаете ли оправданное применение макросов от наследия C. Следом часто спрашивают, чем #define хуже constexpr и что такое include guard.
Как отвечать
Препроцессор это ранняя фаза трансляции: он обрабатывает строки, начинающиеся с #, и отдаёт компилятору уже подставленный текст. Основные задачи:
- подключение заголовков:
#includeбуквально вставляет содержимое файла. Повторное включение предотвращают стражи#ifndef FOO_H,#define FOO_H,#endifили#pragma once, которая не входит в стандарт, но поддерживается GCC, Clang и MSVC; - условная компиляция:
#if,#ifdef,#elif,#else,#endifдля кода под платформу (_WIN32,__linux__), компилятор или конфигурацию сборки.NDEBUGотключаетassert. С C++17 есть__has_include, с C++23#elifdefи стандартный#warning; - макросы:
#defineи#undef, плюс встроенные__FILE__,__LINE__,__cplusplus; - служебное:
#errorостанавливает сборку с понятным сообщением,#pragmaпередаёт указания компилятору.
Сильная часть ответа: где макросы сегодня заменены средствами языка. Константы пишут через constexpr (а число пи есть в std::numbers::pi с C++20), функции-макросы через inline-функции и шаблоны, ветвление по типу через if constexpr, место в коде через std::source_location. Модули C++20 и import std; из C++23 постепенно вытесняют #include, хотя поддержка в сборочных системах пока неровная.
Где препроцессор по-прежнему уместен: платформенный код, макросы экспорта вроде __declspec(dllexport), feature-test макросы из <version>.
Где спотыкаются
Пишут функцию-макрос без скобок: #define SQR(x) x*x превращает SQR(a + 1) в a + 1*a + 1. А SQR(i++) вычисляет аргумент дважды.
Забывают, что макрос не знает пространств имён. Классика: min и max из <windows.h> ломают std::min, лечится NOMINMAX перед включением.
Называют #pragma once частью стандарта или путают #include с #import, который относится к Objective-C и расширениям MSVC, а не к C++.