До выхода C++20 сборка проектов принципиально не менялась около сорока лет. Мы послушно тащили за собой наследие языка C: пилили код на заголовочные файлы (.h / .hpp) и файлы реализации (.cpp), а потом склеивали их через текстовую вставку #include.
С появлением C++20 эта эпоха наконец-то уходит. На смену неудобным хедерам пришли Модули (Modules). Давайте разберемся, почему старый подход всех достал, как устроена новая система и как запустить свой первый модуль.
В чем проблема старого доброго #include?
Когда вы пишете #include iostream, препроцессор не делает никакой магии. Он буквально берет весь текст этого гигантского файла (вместе со всеми его внутренними зависимостями) и вставляет его в ваш .cpp файл прямо перед компиляцией.
Из-за этого разработчики постоянно спотыкались о три проблемы:
- Компиляция со скоростью улитки. Если один и тот же популярный хедер подключен в 50 разных файлов проекта, компилятор будет честно разбирать его текст все 50 раз. В больших проектах это выливается в часы мучительного ожидания сборки.
- Война макросов. Если в каком-нибудь стороннем хедере прописан макрос
#define max 100, он без спроса пролезет во все файлы, куда импортирован этот заголовок. И если у вас где-то была своя переменная или функцияmax, код внезапно и очень загадочно сломается. - Хрупкий порядок подключения. Стоит случайно поменять местами
#include "A.h"и#include "B.h", как проект может рассыпаться с сотней невнятных ошибок просто потому, что один файл неявно зависел от объявлений в другом.
Модули в C++20 решают эти болячки разом. Они компилируются в бинарный формат всего один раз, не тормозят сборку соседних файлов, изолируют макросы внутри себя и не зависят от того, в каком порядке их подключают.
Анатомия модуля: три главных ключевых слова
Чтобы работать с модулями, в синтаксис C++ добавили новые зарезервированные слова. Всё предельно логично:
export module имя_модуля;— этой строчкой мы говорим компилятору, что данный файл является модулем.export— маркер видимости. Ставится перед классами, функциями или переменными, которые мы хотим отдать «наружу».import имя_модуля;— подключает готовый модуль там, где мы хотим его использовать.
Пишем первый модуль своими руками
Давайте создадим простую библиотеку для математических функций и вызовем ее в основной программе. Больше никаких .h файлов!
Шаг 1. Создаем интерфейс модуля
Создайте файл с расширением .cppm (универсальный стандарт для большинства компиляторов) или .ixx (если работаете чисто в Visual Studio). Назовем его MathUtils.cppm.
// Объявляем модуль с именем MathUtils export module MathUtils; // Подключаем стандартную библиотеку в модульном стиле import iostream; // Эта функция будет доступна везде, где импортирован модуль export int add(int a, int b) { return a + b; } // Эта функция тоже открыта для внешнего мира export void printMessage() { std::cout << "Привет из модуля MathUtils!" << std::endl; } // А вот эта функция НЕ экспортируется. // Она останется скрытой деталью реализации внутри модуля. void secretInternalFunction() { // Внутренняя логика... }
Шаг 2. Подключаем модуль в основной программе
Теперь создаем обычный файл main.cpp. Обратите внимание: никаких хедеров нам больше не нужно.
// Подключаем наш собственный модуль import MathUtils; int main() { // Спокойно вызываем функции из модуля printMessage(); int result = add(5, 7); // Ошибка компиляции! Функция secretInternalFunction() не экспортировалась, // поэтому из main.cpp мы ее не видим. // secretInternalFunction(); return 0; }
Сравнение: старая школа против новой
| Критерий | Старый #include |
Новый import |
|---|---|---|
| Как работает | Тупо копирует текст файла «как есть» | Подключает скомпилированный бинарный интерфейс |
| Скорость сборки | Медленно (парсинг повторяется многократно) | Молниеносно (компилируется 1 раз) |
| Защита от макросов | Отсутствует (макросы утекают и ломают код) | Полная изоляция внутри модуля |
| Инкапсуляция | Всё, что есть в .h файле, торчит наружу |
Видно только то, что помечено словом export |
Как пощупать модули прямо сейчас?
Современные компиляторы уже отлично дружат с модулями, но иногда им нужно немного помочь настройками.
- В Visual Studio (MSVC): Создайте C++ проект, зайдите в Свойства проекта -> Свойства конфигурации -> Общие. Убедитесь, что в поле «Стандарт языка C++» стоит ISO C++20 (или новее). Студия автоматически поймет, что файлы
.ixx— это модули. - В GCC / Clang: Для сборки пока еще нужно явно указывать флаги. Например:
-std=c++20 -fmodules-ts.
Резюме: Модули — это глоток свежего воздуха для экосистемы C++. Они избавляют от бесконечной возни с заголовками, ускоряют сборку и делают архитектуру чище. Если стартуете новый проект или пишете библиотеку с нуля — самое время оставить #include в прошлом.
