Пошаговое руководство. Сборка и импорт блоков заголовков в Microsoft Visual C++
В этой статье описывается создание и импорт блоков заголовков с помощью Visual Studio 2022. Сведения о импорте заголовков стандартной библиотеки C++ в виде блоков заголовков см. в пошаговом руководстве. Импорт библиотек STL в виде единиц заголовка. Более быстрый и надежный способ импорта стандартной библиотеки см. в руководстве по импорту стандартной библиотеки C++ с помощью модулей.
В качестве альтернативы файлам предкомпилированных заголовков (PCH) рекомендуется использовать блоки заголовков. Единицы заголовков проще настроить и использовать, значительно меньше на диске, обеспечивают аналогичные преимущества производительности и являются более гибкими, чем общий PCH.
Чтобы контрастировать блоки заголовков с другими способами включения функциональных возможностей в программы, см . раздел "Сравнение единиц заголовков", модулей и предварительно скомпилированных заголовков.
Необходимые компоненты
Для использования блоков заголовков требуется Visual Studio 2019 версии 16.10 или более поздней.
Что такое блок заголовка
Блок заголовка — это двоичное представление файла заголовка. Блок заголовка заканчивается расширением .ifc
. Тот же формат используется для именованных модулей.
Важное различие между единицей заголовка и файлом заголовка заключается в том, что блок заголовка не влияет на определения макросов за пределами единицы заголовка. То есть нельзя определить символ препроцессора, который приводит к тому, что блок заголовка будет вести себя по-разному. К тому времени, когда вы импортируете блок заголовка, блок заголовка уже компилируется. Это отличается от способа #include
обработки файла. Включенный файл может повлиять на определение макроса вне файла заголовка, так как файл заголовка проходит через препроцессор при компиляции исходного файла, который включает его.
Блоки заголовков можно импортировать в любом порядке, что не относится к файлам заголовков. Порядок файлов заголовков имеет значение, так как определения макросов, определенные в одном файле заголовка, могут повлиять на последующий файл заголовка. Определения макросов в одной единице заголовка не могут повлиять на другую единицу заголовка.
Все, видимое из файла заголовка, также отображается из единицы заголовка, включая макросы, определенные в единице заголовка.
Перед импортом файла заголовка необходимо преобразовать в блок заголовка. Преимущество блоков заголовков над предварительно скомпилированных файлов заголовков (PCH) заключается в том, что они могут использоваться в распределенных сборках. Пока вы компилируете и программу, импортируемую с тем же компилятором, и нацелив .ifc
на ту же платформу и архитектуру, единицу заголовка, созданную на одном компьютере, можно использовать на другом. В отличие от PCH, когда единица заголовка изменяется, только она и то, что зависит от нее, перестроены. Единицы заголовков могут быть до порядка величины меньше размера, чем единица .pch
.
Единицы заголовков накладывают меньше ограничений на необходимые сходства сочетаний коммутаторов компилятора, используемых для создания единицы заголовка и компиляции кода, который использует его, чем PCH. Однако некоторые сочетания коммутаторов и определения макросов могут создавать нарушения одного правила определения (ODR) между различными единицами перевода.
Наконец, единицы заголовков являются более гибкими, чем PCH. С помощью PCH вы не можете использовать только один из заголовков в PCH-компиляторе все из них. С блоками заголовков даже при компиляции их в статическую библиотеку вы переносите только содержимое единицы заголовка, импортируемой в приложение.
Блоки заголовков — это шаг между файлами заголовков и модулями C++20. Они предоставляют некоторые преимущества модулей. Они более надежны, так как внешние определения макросов не влияют на них, поэтому их можно импортировать в любом порядке. И компилятор может обрабатывать их быстрее, чем файлы заголовков. Но единицы заголовков не имеют всех преимуществ модулей, так как блоки заголовков предоставляют макросы, определенные в них (модули не имеют). В отличие от модулей, нет способа скрыть частную реализацию в блоке заголовков. Чтобы указать частную реализацию с файлами заголовков, используются различные методы, такие как добавление символов подчеркивания в имена или размещение в пространстве имен реализации. Модуль не предоставляет частную реализацию в какой-либо форме, поэтому это не нужно делать.
Попробуйте заменить предварительно скомпилированные заголовки единицами заголовков. Вы получаете то же преимущество скорости, но с другими преимуществами гигиены и гибкости кода, а также.
Способы компиляции блока заголовка
Существует несколько способов компиляции файла в блок заголовка:
Создание проекта с общим заголовком. Мы рекомендуем этот подход, так как он обеспечивает больший контроль над организацией и повторное использование импортированных единиц заголовков. Создайте проект статической библиотеки, содержащий нужные блоки заголовков, а затем со ссылкой на него, чтобы импортировать единицы заголовков. Пошаговое руководство по этому подходу см. в разделе "Создание проекта статической библиотеки блока заголовка" для единиц заголовков.
Выберите отдельные файлы для преобразования в блоки заголовков. Этот подход позволяет управлять тем, что рассматривается как единица заголовка. Это также полезно, если необходимо скомпилировать файл как блок заголовка, так как он не имеет расширения по умолчанию (
.ixx
,.cppm
, ,.h
),.hpp
обычно не будет компилироваться в единицу заголовка. Именно этот подход демонстрируется в этом пошаговом руководстве. Сведения о начале работы см. в разделе "Подход 1. Перевод определенного файла в единицу заголовка".Автоматически сканировать и создавать блоки заголовков. Этот подход удобнее, но лучше всего подходит для небольших проектов, так как он не гарантирует оптимальную пропускную способность сборки. Дополнительные сведения об этом подходе см . в разделе "Подход 2. Автоматическое сканирование единиц заголовка".
Как упоминалось в этой статье, вы можете создавать и импортировать файлы заголовков STL в виде единиц заголовков и автоматически обрабатывать
#include
заголовки библиотек STL, какimport
без перезаписи кода. Чтобы узнать, как это сделать, посетите пошаговое руководство. Импорт библиотек STL в качестве единиц заголовка.
Подход 1. Перевод определенного файла в единицу заголовка
В этом разделе показано, как выбрать конкретный файл для преобразования в единицу заголовка. Скомпилируйте файл заголовка в виде единицы заголовка, выполнив следующие действия в Visual Studio:
Создайте проект консольного приложения С++.
Замените содержимое исходного файла следующим кодом:
#include "Pythagorean.h" int main() { PrintPythagoreanTriple(2,3); return 0; }
Добавьте вызываемый файл
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++ стандартный или /std:c++20
более поздней версии следующие действия:
- В Обозреватель решений щелкните правой кнопкой мыши имя проекта и выберите "Свойства".
- В левой области окна страницы со свойствами проекта выберите Свойства конфигурации>Общие.
- В раскрывающемся списке C++ Language Standard выберите ISO C++20 Standard (/std:c++20) или более поздней версии. Нажмите кнопку "ОК", чтобы закрыть диалоговое окно.
Скомпилируйте файл заголовка в виде единицы заголовка:
В Обозреватель решений выберите файл, который нужно скомпилировать как блок заголовка (в данном случае
Pythagorean.h
). Щелкните файл правой кнопкой мыши и выберите пункт "Свойства".Задайте раскрывающийся список свойств>конфигурации общего>типа элемента компилятором C/C++ и нажмите кнопку "ОК".
При сборке этого проекта далее в этом пошаговом руководстве Pythagorean.h
будет переведено в блок заголовка. Он преобразуется в единицу заголовка, так как тип элемента для этого файла заголовка имеет значение компилятор C/C++, и так как действие по умолчанию для .h
и .hpp
файлов, заданных таким образом, заключается в переводе файла в единицу заголовка.
Примечание.
Это не обязательно для этого пошагового руководства, но предоставляется для вашей информации. Чтобы скомпилировать файл в виде блока заголовка, который не имеет расширения модуля блока заголовка по умолчанию, например.cpp
, задайте свойства>конфигурации C/C++>Advanced>Compile As to Compile as C++ Header Unit (/exportHeader):
Изменение кода для импорта единицы заголовка
В исходном файле примера проекта измените значение
#include "Pythagorean.h"
неimport "Pythagorean.h";
забывайте о запятой. Это необходимо дляimport
инструкций. Так как это файл заголовка в локальном каталоге проекта, мы использовали кавычки с инструкциейimport
:import "file";
В собственных проектах для компиляции единицы заголовка из системного заголовка используйте угловые скобки:import <file>;
Скомпилируйте решение, выбрав Сборка>Собрать решение в главном меню. Запустите его, чтобы увидеть, что он создает ожидаемые выходные данные:
Pythagorean triple a:2 b:3 c:13
В собственных проектах повторите эту процедуру, чтобы скомпилировать файлы заголовков, которые нужно импортировать в качестве блоков заголовков.
Если вы хотите преобразовать только несколько файлов заголовков в единицы заголовков, этот подход подходит. Но если у вас есть много файлов заголовков, которые вы хотите скомпилировать, и потенциальная потеря производительности сборки перевешивается удобством автоматической обработки системы сборки, см. следующий раздел.
Если вы хотите специально импортировать заголовки библиотек STL в качестве единиц заголовков, см . пошаговое руководство. Импорт библиотек STL в качестве единиц заголовка.
Подход 2. Автоматическое сканирование и блоки заголовков сборки
Так как требуется время для сканирования всех исходных файлов для единиц заголовков и времени их сборки, следующий подход лучше подходит для небольших проектов. Это не гарантирует оптимальную пропускную способность сборки.
Этот подход объединяет два параметра проекта Visual Studio:
- Источники сканирования для зависимостей модулей вызывают систему сборки для вызова компилятора, чтобы убедиться, что все импортированные модули и блоки заголовков создаются перед компиляцией файлов, зависящих от них. При сочетании с преобразованием "Включает в импорт" все файлы заголовков, включенные в источник, которые также указаны в
header-units.json
файле, расположенном в том же каталоге, что и файл заголовка, компилируются в единицы заголовков. - Преобразование "Включает в импорт " обрабатывает файл заголовка как
import
файл заголовка, если#include
он ссылается на файл заголовка, который можно скомпилировать как единицу заголовка (как указано вheader-units.json
файле), а скомпилированный блок заголовка доступен для файла заголовка. В противном случае файл заголовка рассматривается как обычный#include
. Файлheader-units.json
используется для автоматической сборки блоков заголовков для каждого#include
из них без дублирования символов.
Эти параметры можно включить в свойствах проекта. Для этого щелкните проект правой кнопкой мыши в Обозреватель решений и выберите "Свойства". Затем выберите свойства>конфигурации C/C++>General.
Источники сканирования для зависимостей модуля можно задать для всех файлов проекта в свойствах проекта, как показано здесь, или для отдельных файлов в свойствах файла. Модули и блоки заголовков всегда сканируются. Установите этот параметр, если у вас есть .cpp
файл, который импортирует блоки заголовков, которые вы хотите создать автоматически и может еще не создаваться.
Эти параметры работают вместе, чтобы автоматически создавать и импортировать блоки заголовков в следующих условиях:
- Источники сканирования для зависимостей модуля сканируют источники файлов и их зависимостей, которые можно рассматривать как единицы заголовков. Файлы с расширением
.ixx
и файлами с их свойствами>файла C/C++>Compile As, заданными для компиляции как единица заголовка C++ (/export), всегда сканируются независимо от этого параметра. Компилятор также ищетimport
инструкции для идентификации зависимостей единиц заголовка. Если/translateInclude
задано, компилятор также проверяет#include
директивы, которые также указаны вheader-units.json
файле для обработки как единиц заголовка. Граф зависимостей создается из всех модулей и блоков заголовков в проекте. - Перевод включает в импорт , когда компилятор обнаруживает
#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 в качестве блоков заголовков