Пошаговое руководство. Создание и импорт единиц заголовков в Microsoft C++

В этой статье описывается создание и импорт блоков заголовков с помощью Visual Studio 2022. Сведения о том, как импортировать заголовки стандартной библиотеки C++ в виде единиц заголовков, см. в пошаговом руководстве: Импорт библиотек STL как единиц заголовков. Более быстрый и надежный способ импорта стандартной библиотеки см. в руководстве по импорту стандартной библиотеки C++ с помощью модулей.

В качестве альтернативы файлам предкомпилированных заголовков (PCH) рекомендуется использовать блоки заголовков. Единицы заголовков (header units) проще настроить и использовать, они занимают значительно меньше места на диске, обеспечивают аналогичные преимущества в производительности и более гибкие, чем общий PCH.

Чтобы контрастировать блоки заголовков с другими способами включения функциональных возможностей в программы, см . раздел "Сравнение единиц заголовков", модулей и предварительно скомпилированных заголовков.

Предварительные условия

Для использования блоков заголовков требуется Visual Studio 2019 версии 16.10 или более поздней.

Что такое блок заголовка

Блок заголовка — это двоичное представление файла заголовка. Блок заголовка заканчивается расширением .ifc. Тот же формат используется для именованных модулей.

Важное различие между единицей заголовка и файлом заголовка заключается в том, что блок заголовка не влияет на определения макросов за пределами единицы заголовка. То есть нельзя определить символ препроцессора, который приводит к тому, что блок заголовка будет вести себя по-разному. К тому времени, когда вы импортируете блок заголовка, блок заголовка уже компилируется. Это отличается от способа #include обработки файла. Включаемый файл может быть затронут определением макроса, находящимся вне файла заголовка, так как, при компиляции исходного файла, который его включает, файл заголовка проходит через препроцессор.

Блоки заголовков можно импортировать в любом порядке, что не относится к файлам заголовков. Порядок файлов заголовков имеет значение, так как определения макросов, определенные в одном файле заголовка, могут повлиять на последующий файл заголовка. Определения макросов в одной единице заголовка не могут повлиять на другую единицу заголовка.

Все, что видно из файла заголовка, также видно из модуля заголовка, включая макросы, определенные в модуле заголовка.

Перед импортом файл заголовка должен быть преобразован в единицу заголовка. Преимущество блоков заголовков над предварительно скомпилированных файлов заголовков (PCH) заключается в том, что они могут использоваться в распределенных сборках. Пока вы компилируете .ifc и программу, которая его импортирует, с тем же компилятором и нацеливаетесь на ту же платформу и архитектуру, единица заголовка, созданная на одном компьютере, может использоваться на другом. В отличие от PCH, когда единица заголовка изменяется, перестраиваются только она и те, кто зависит от нее. Единицы заголовков могут быть в несколько раз меньше по размеру, чем .pch.

Единицы заголовков накладывают меньше ограничений на необходимые сходства комбинаций параметров компилятора, используемых при создании единицы заголовка и компиляции кода, который её использует, чем это делает PCH. Однако некоторые сочетания переключателей и макроопределения могут создавать нарушения правила одного определения (ODR) между различными трансляционными единицами.

Наконец, единицы заголовков являются более гибкими, чем PCH. С помощью PCH вы не можете выбрать подключение только одного из заголовков — компилятор обрабатывает их все. С блоками заголовков даже при компиляции их в статическую библиотеку вы переносите только содержимое единицы заголовка, импортируемой в приложение.

Блоки заголовков — это шаг между файлами заголовков и модулями C++20. Они предоставляют некоторые преимущества модулей. Они более надежны, так как внешние определения макросов не влияют на них, поэтому их можно импортировать в любом порядке. И компилятор может обрабатывать их быстрее, чем файлы заголовков. Но заголовочные единицы не имеют всех преимуществ модулей, так как заголовочные единицы разглашают макросы, определенные в них (чего не делают модули). В отличие от модулей, нет способа скрыть частную реализацию в блоке заголовков. Чтобы указать частную реализацию с файлами заголовков, используются различные методы, такие как добавление ведущих символов подчеркивания к именам или размещение элементов в пространстве имен реализации. Модуль не предоставляет частную реализацию в какой-либо форме, поэтому это не нужно делать.

Попробуйте заменить предварительно скомпилированные заголовки модулями заголовков. Вы получаете такое же преимущество в скорости, а также дополнительные преимущества в чистоте и гибкости кода.

Способы компиляции блока заголовка

Существует несколько способов компиляции файла в блок заголовка:

  • Создание проекта со общим модулем-заголовком. Мы рекомендуем этот подход, так как он обеспечивает больший контроль над организацией и повторное использование импортированных единиц заголовков. Создайте проект статической библиотеки, содержащий нужные блоки заголовков, а затем со ссылкой на него, чтобы импортировать единицы заголовков. Пошаговое руководство по этому подходу см. в разделе "Создание проекта статической библиотеки блока заголовка" для единиц заголовков.

  • Выберите отдельные файлы для преобразования в блоки заголовков. Этот подход обеспечивает поконтрольное управление каждым файлом, чтобы определить, что будет считаться единицей заголовка. Это также полезно, если необходимо скомпилировать файл как единицу заголовка, поскольку он не имеет расширения по умолчанию (.ixx, .cppm, .h, .hpp), обычно не будет компилироваться в единицу заголовка. Именно этот подход демонстрируется в этом пошаговом руководстве. Чтобы начать, см. Подход 1: Перевод конкретного файла в заголовочную единицу.

  • Автоматически сканировать и создавать блоки заголовков. Этот подход удобнее, но лучше всего подходит для небольших проектов, так как он не гарантирует оптимальную пропускную способность сборки. Дополнительные сведения об этом подходе см. в разделе Подход 2: Автоматическое сканирование единиц заголовка.

  • Как упоминалось во введении, вы можете создавать и импортировать файлы заголовков STL в виде единиц заголовков и автоматически обрабатывать #include для заголовков библиотек STL как import без перезаписи вашего кода. Чтобы узнать, как это сделать, посетите Пошаговое руководство: Импорт библиотек STL в качестве единиц заголовка.

Подход 1. Перевод определенного файла в единицу заголовка

В этом разделе показано, как выбрать конкретный файл для преобразования в единицу заголовка. Скомпилируйте файл заголовка как модуль заголовков, выполнив следующие действия в Visual Studio:

  1. Создайте проект консольного приложения С++.

  2. Замените содержимое исходного файла следующим кодом:

    #include "Pythagorean.h"
    
    int main()
    {
        PrintPythagoreanTriple(2,3);
        return 0;
    }
    
  3. Добавьте файл заголовка с именем Pythagorean.h, а затем замените его содержимое следующим кодом:

    #ifndef PYTHAGOREAN
    #define PYTHAGOREAN
    
    #include <iostream>
    
    inline void PrintPythagoreanTriple(int a, int b)
    {
        std::cout << "Pythagorean triple a:" << a << " b:" << b << " c:" << a*a + b*b << std::endl;
    }
    #endif
    

Настройка свойств проекта

Чтобы включить единицы заголовков, сначала установите для языка C++ стандарт или более поздней версии, используя следующие шаги:

  1. В Обозреватель решений щелкните правой кнопкой мыши имя проекта и выберите "Свойства".
  2. В левой области окна страницы со свойствами проекта выберите Свойства конфигурации>Общие.
  3. В раскрывающемся списке C++ Language Standard выберите ISO C++20 Standard (/std:c++20) или более поздней версии. Нажмите кнопку "ОК", чтобы закрыть диалоговое окно.

Скомпилируйте файл заголовка как модуль заголовка:

  1. В Обозреватель решений выберите файл, который нужно скомпилировать как блок заголовка (в данном случаеPythagorean.h). Щелкните файл правой кнопкой мыши и выберите пункт "Свойства".

  2. Установите раскрывающийся список Свойства конфигурации>Общие>Тип элемента на Компилятор C/C++ и выберите ОК.

    Снимок экрана, на котором показано изменение типа элемента на

При сборке этого проекта далее в этом пошаговом руководстве Pythagorean.h будет переведено в заголовочный модуль. Он преобразуется в единицу заголовка, так как тип элемента для этого файла заголовка имеет значение компилятор C/C++, и так как действие по умолчанию для .h и .hpp файлов, заданных таким образом, заключается в переводе файла в единицу заголовка.

Примечание.

Это не обязательно для этого пошагового руководства, но предоставляется для вашей информации. Чтобы скомпилировать файл в виде блока заголовка, который не имеет расширения модуля блока заголовка по умолчанию, например, .cpp установите свойства конфигурации>C/C++> Дополнительно> Компиляция как в Компилировать как заголовочный блок C++ (/exportHeader): Снимок экрана, показывающий изменение свойств конфигурации > C/C++ > Дополнительно > Компиляция как на Компилировать как заголовочный блок C++ (/exportHeader).

Измените код для импорта модуля заголовка

  1. В исходном файле проекта примера измените #include "Pythagorean.h" на import "Pythagorean.h";. Не забудьте о точке с запятой. Это необходимо для import инструкций. Так как это файл заголовка в локальном каталоге проекта, мы использовали кавычки с инструкциейimport: import "file"; В собственных проектах для компиляции единицы заголовка из системного заголовка используйте угловые скобки: import <file>;

  2. Соберите решение, выбрав Построить>Построить решение в главном меню. Запустите его, чтобы увидеть, что он создает ожидаемые выходные данные: Pythagorean triple a:2 b:3 c:13

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

Если вы хотите преобразовать только несколько файлов заголовков в единицы заголовков, этот подход подходит. Но если у вас есть много файлов заголовков, которые вы хотите скомпилировать, и потенциальная потеря производительности сборки перевешивается удобством автоматической обработки системы сборки, см. следующий раздел.

Если вы хотите специально импортировать заголовки библиотек STL в качестве модулей заголовков, см. Пошаговое руководство: Импорт библиотек STL в качестве модулей заголовков.

Подход 2: Автоматическое сканирование и создание юнитов заголовочных файлов

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

Этот подход объединяет два параметра проекта Visual Studio:

  • Сканирование источников для зависимостей модуля заставляет систему сборки вызывать компилятор, чтобы убедиться, что все импортированные модули и блоки заголовков созданы до компиляции файлов, которые от них зависят. При сочетании с Преобразованием "Включает в импорт", все файлы заголовков, включенные в ваш исходный код и также указанные в header-units.json файле, который находится в том же каталоге, что и файл заголовка, компилируются в единицы заголовков.
  • Перевод "Includes" в "Imports" обрабатывает файл заголовка как import, если #include ссылается на файл заголовка, который можно скомпилировать как модуль заголовка (как указано в файле header-units.json), и если скомпилированный модуль заголовка доступен для данного файла заголовка. В противном случае файл заголовка рассматривается как обычный #include. Файл header-units.json используется для автоматической сборки заголовков для каждого #include, без дублирования символов.

Эти параметры можно включить в свойствах проекта. Для этого щелкните проект правой кнопкой мыши в Обозреватель решений и выберите "Свойства". Затем выберите Свойства конфигурации>C/C++>Общие.

Снимок экрана: окно свойств проекта с выделенной конфигурацией и выбранным параметром

Источники сканирования для зависимостей модуля можно задать для всех файлов проекта в свойствах проекта, как показано здесь, или для отдельных файлов в свойствах файла. Модули и блоки заголовков всегда сканируются. ** Установите этот параметр, если у вас есть .cpp файл, который импортирует единицы заголовка, которые вы хотите создать автоматически и которые еще могут не быть создан.

Эти параметры работают вместе, чтобы автоматически создавать и импортировать блоки заголовков в следующих условиях:

  • Сканирование исходников на зависимости модулей выполняет проверку исходников на файлы и их зависимости, которые могут рассматриваться как заголовочные единицы. Файлы с расширением .ixx, а также файлы, у которых свойство Свойства файла>C/C++>Компиляция как установлено на Компиляция как C++ Header Unit (/export), всегда сканируются независимо от этого параметра. Компилятор также ищет import инструкции для идентификации зависимостей единиц заголовка. Если /translateInclude указано, компилятор также сканирует директивы #include, которые указаны в файле header-units.json, чтобы рассматривать их как единицы заголовков. Граф зависимостей создается из всех модулей и блоков заголовков в проекте.
  • Переведите Includes в Imports, когда компилятор обнаруживает #include инструкцию, и единственный файл заголовка существует для указанного заголовочного файла.ifc, компилятор импортирует единицу заголовка вместо того, чтобы рассматривать его как обычный файл заголовка #include. В сочетании с сканированием зависимостей компилятор находит все файлы заголовков, которые можно скомпилировать в единицы заголовков. Список разрешений обращается к компилятору, чтобы решить, какие файлы заголовков могут компилироваться в единицы заголовков. Этот список хранится в header-units.json файле, который должен находиться в том же каталоге, что и включенный файл. Пример header-units.json файла можно просмотреть в каталоге установки для Visual Studio. Например, компилятор использует %ProgramFiles%\Microsoft Visual Studio\2022\Enterprise\VC\Tools\MSVC\14.30.30705\include\header-units.json для определения того, можно ли заголовок стандартной библиотеки шаблонов скомпилировать в модуль заголовков. Эта функция существует, чтобы служить мостом с устаревшим кодом, чтобы получить некоторые преимущества единиц заголовков.

Файл header-units.json служит двумя целями. Помимо указания, какие файлы заголовков можно скомпилировать в единицы заголовков, он сводит к минимуму повторяющиеся символы, чтобы увеличить производительность сборки. Дополнительные сведения о дублировании символов см. в header-units.json справочнике по C++ .

Эти коммутаторы и header-unit.json предоставляют некоторые преимущества единиц заголовка. Удобство достигается за счет производительности сборки. Этот подход может быть не лучшим для крупных проектов, так как он не гарантирует оптимальное время сборки. Кроме того, те же файлы заголовков могут повторно обрабатываться повторно, что увеличивает время сборки. Однако может стоить учитывать удобства в зависимости от проекта.

Эти функции предназначены для устаревшего кода. Для нового кода используйте модули вместо заголовочных единиц или #include файлов. Для ознакомления с использованием модулей смотрите руководство по модулям имен (C++).

Пример использования этого метода для импорта файлов заголовков STL в виде единиц заголовка см. в пошаговом руководстве. Импорт библиотек STL в качестве единиц заголовка.

Последствия препроцессора

Стандартный препроцессор, соответствующий C99/C++11, требуется для создания и использования блоков заголовков. Компилятор включает новый препроцессор C99/C++11 при компиляции единиц заголовков путем неявного добавления /Zc:preprocessor в командную строку, когда используется любой вид /exportHeader. Попытка отключить ее приведет к ошибке компиляции.

Включение нового препроцессора затрагивает обработку вариативных макросов. Для получения дополнительной информации см. раздел «Примечания о макросах Variadic».

См. также

/translateInclude
/exportHeader
/headerUnit
header-units.json
Сравнение единиц заголовков, модулей и предварительно скомпилированных заголовков
Обзор модулей в C++
Руководство. Импорт стандартной библиотеки C++ с помощью модулей
Пошаговое руководство. Импорт библиотек STL в качестве блоков заголовков