Поделиться через


Руководство. Импорт стандартной библиотеки C++ с помощью модулей из командной строки

Узнайте, как импортировать стандартную библиотеку C++ с помощью модулей библиотекИ C++. Это приводит к более быстрой компиляции и более надежной, чем использование файлов заголовков или блоков заголовков или предварительно скомпилированных заголовков (PCH).

В этом руководстве описано:

  • Как импортировать стандартную библиотеку в виде модуля из командной строки.
  • Преимущества производительности и удобства использования модулей.
  • Два стандартных модуля библиотеки std и std.compat разница между ними.

Предпосылки

Для работы с этим руководством требуется Visual Studio 2022 17.5 или более поздней версии.

Общие сведения о модулях стандартной библиотеки

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

Теперь можно импортировать стандартную библиотеку в виде модуля вместо сложного набора файлов заголовков. Это гораздо быстрее и надежнее, чем в том числе файлы заголовков, блоки заголовков или предварительно скомпилированные заголовки (PCH).

Стандартная библиотека C++23 содержит два именованных модуля: std и std.compat:

  • std экспортирует объявления и имена, определенные в пространстве имен стандартной библиотеки C++ std, например std::vector. Он также экспортирует содержимое заголовков оболочки C, таких как <cstdio> и <cstdlib>, которые предоставляют такие функции, как std::printf(). Функции C, определенные в глобальном пространстве имен, например ::printf(), не экспортируются. Это улучшает ситуацию, когда включение заголовка-оболочки C, такого как <cstdio>also, также влечет за собой подключение C-заголовков, таких как stdio.h, которые добавляют версии глобальных пространств имен C. Это не станет проблемой при импорте std.
  • std.compat экспортирует все в std и добавляет глобальные пространства имен среды выполнения C, такие как ::printf, ::fopen, ::size_t, ::strlen и т. д. Модуль std.compat упрощает работу с базами кода, которые ссылаются на многие функции и типы среды выполнения C в глобальном пространстве имен.

Компилятор импортирует всю стандартную библиотеку при использовании import std; или import std.compat;, и делает это быстрее, чем при импортировании одного файла заголовка. Это быстрее подключить всю стандартную библиотеку с import std; (или import std.compat), чем #include <vector>, например.

Так как именованные модули не предоставляют макросы, макросы, например assert, errno, offsetofи va_argдругие, недоступны при импорте std или std.compat. См. раздел Соображения по именованным модулям стандартной библиотеки для получения обходных решений.

Сведения о модулях C++

Файлы заголовков — это способ обмена объявлениями и определениями между исходными файлами в C++. До модулей стандартной библиотеки было необходимо включать ту часть стандартной библиотеки, которая требовалась, с помощью такой директивы, как #include <vector>. Файлы заголовков являются хрупкими и сложными, так как их семантика может изменяться в зависимости от порядка их включения или определения определенных макросов. Они также медленно компилируются, так как они повторно обрабатывается каждым исходным файлом, который включает их.

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

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

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

Импорт стандартной библиотеки с помощью std

В следующих примерах показано, как использовать стандартную библиотеку в качестве модуля с помощью компилятора командной строки. Сведения о том, как это сделать в интегрированной среде разработки Visual Studio, см. в разделе "Сборка модулей стандартной библиотеки ISO C++23".

Инструкция import std; или import std.compat; импорт стандартной библиотеки в приложение. Но сначала необходимо скомпилировать стандартные именованные модули библиотеки в двоичную форму. Ниже показано, как это сделать.

Пример: Как создать и импортировать std

  1. Откройте командную строку инструментов x86 Native для VS: в меню Пуск Windows введите x86 native и командная строка отобразится в списке приложений. Убедитесь, что запрос предназначен для Visual Studio 2022 версии 17.5 или выше. При использовании неправильной версии запроса возникают ошибки. Примеры, используемые в этом руководстве, предназначены для оболочки CMD.

  2. Создайте каталог, например %USERPROFILE%\source\repos\STLModulesи сделайте его текущим каталогом. Если вы выберете директорию, к которой у вас нет доступа на запись, вы получите ошибки во время компиляции.

  3. Скомпилируйте именованный модуль с помощью следующей std команды:

    cl /std:c++latest /EHsc /nologo /W4 /c "%VCToolsInstallDir%\modules\std.ixx"
    

    Если возникают ошибки, убедитесь, что вы используете правильную версию командной строки.

    Скомпилируйте именованный std модуль, используя те же параметры компилятора, которые вы планируете использовать с кодом, импортируемым встроенным модулем. Если у вас есть решение с несколькими проектами, можно скомпилировать стандартную библиотеку с именем модуля один раз, а затем обратиться к нему из всех проектов с помощью параметра компилятора /reference .

    Используя предыдущую команду компилятора, компилятор выводит два файла:

    • std.ifc — это скомпилированное двоичное представление именованного интерфейса модуля, к которому компилятор обращается для обработки оператора import std;. Это артефакт, существующий только на этапе компиляции. Он не поставляется с вашим приложением.
    • std.obj содержит реализацию именованного модуля. Добавьте std.obj в командную строку при компиляции примера приложения для статического связывания функциональных возможностей, используемых из стандартной библиотеки в приложение.

    Основные параметры командной строки в этом примере:

    Выключатель Meaning
    /std:c++latest Используйте последнюю версию стандарта языка C++ и библиотеки. Хотя поддержка модулей доступна в разделе /std:c++20, вам нужна последняя стандартная библиотека, чтобы получить поддержку стандартных именованных модулей библиотеки.
    /EHsc Используйте обработку исключений C++ за исключением функций, помеченных extern "C".
    /W4 /W4 В целом использование рекомендуется, особенно для новых проектов, поскольку он включает все уровень 1, уровень 2, уровень 3 и большинство предупреждений уровня 4 (информационные), которые могут помочь вам выявлять потенциальные проблемы на ранних стадиях. По сути, он предоставляет предупреждения, похожие на линт, которые могут помочь минимизировать труднодоступные дефекты кода.
    /c Скомпилируйте без связывания, так как мы просто создадим двоичный именованный интерфейс модуля на этом этапе.

    Вы можете управлять именем файла объекта и именем именованного файла интерфейса модуля с помощью следующих переключателей:

    • /Fo задает имя файла объекта. Например: /Fo:"somethingelse". По умолчанию компилятор использует то же имя для файла объекта, что и исходный файл модуля (.ixx). В примере имя файла объекта по умолчанию является std.obj тем, что мы компилируем файл std.ixxмодуля.
    • /ifcOutput задает имя именованного файла интерфейса модуля (.ifc). Например: /ifcOutput "somethingelse.ifc". По умолчанию компилятор использует то же имя для файла интерфейса модуля (.ifc), что и исходный файл модуля (.ixx). В этом примере созданный ifc файл является std.ifc по умолчанию, так как мы компилируем файл модуля std.ixx.
  4. Импортируйте созданную библиотеку std , сначала создав файл с именем importExample.cpp со следующим содержимым:

    // requires /std:c++latest
    
    import std;
    
    int main()
    {
        std::cout << "Import the STL library for best performance\n";
        std::vector<int> v{5, 5, 5};
        for (const auto& e : v)
        {
            std::cout << e;
        }
    }
    

    В приведенном выше коде import std; заменяет #include <vector> и #include <iostream>. import std; Оператор делает всю стандартную библиотеку доступной одной инструкцией. Импорт всей стандартной библиотеки часто гораздо быстрее, чем обработка одного файла заголовка стандартной библиотеки, например #include <vector>.

  5. Скомпилируйте пример с помощью следующей команды в том же каталоге, что и предыдущий шаг:

    cl /c /std:c++latest /EHsc /nologo /W4 /reference "std=std.ifc" importExample.cpp
    link importExample.obj std.obj
    

    В этом примере не требуется указывать /reference "std=std.ifc" в командной строке, так как компилятор автоматически ищет .ifc файл, соответствующий имени модуля, заданному инструкцией import . Когда компилятор обнаруживает import std;, он может найти std.ifc, если тот находится в том же каталоге, что и исходный код. .ifc Если файл находится в каталоге, отличном от исходного кода, используйте параметр компилятора /reference для ссылки на него.

    В этом примере компиляция исходного кода и связывание реализации модуля с приложением являются отдельными шагами. Они не должны быть. Вы можете использовать cl /std:c++latest /EHsc /nologo /W4 /reference "std=std.ifc" importExample.cpp std.obj для компиляции и связывания на одном шаге. Но может быть удобно сборки и связывания проводить раздельно, так как тогда необходимо создать стандартную библиотеку, названную модулем, только один раз, а затем вы можете ссылаться на неё из вашего проекта или из нескольких проектов на этапе связывания вашей сборки.

    Если вы создаете один проект, вы можете объединить шаги по созданию std стандартной библиотеки с именем модуля и этапу создания приложения, добавив "%VCToolsInstallDir%\modules\std.ixx" в командную строку. Поместите его перед любыми .cpp файлами, которые используют std модуль.

    По умолчанию имя выходного исполняемого файла берется из первого входного файла. Используйте параметр компилятора, чтобы указать нужное /Fe имя исполняемого файла. В этом руководстве показано, как скомпилировать именованный std модуль как отдельный шаг, так как необходимо создать стандартную библиотеку с именем модуля один раз, а затем можно ссылаться на него из проекта или из нескольких проектов. Но может быть удобно создать все вместе, как показано в этой командной строке:

    cl /FeimportExample /std:c++latest /EHsc /nologo /W4 "%VCToolsInstallDir%\modules\std.ixx" importExample.cpp
    

    Учитывая предыдущую командную строку, компилятор создает исполняемый файл с именем importExample.exe. При запуске он создает следующие выходные данные:

    Import the STL library for best performance
    555
    

Импорт стандартной библиотеки и глобальных функций C с помощью std.compat

Стандартная библиотека C++ включает стандартную библиотеку ISO C. Модуль std.compat предоставляет все функциональные возможности std модуля, например std::vector, std::cout, std::printfи std::scanfт. д. Но она также предоставляет версии глобального пространства имен таких функций, как ::printf, ::scanf, ::fopenи ::size_tт. д.

Именованный std.compat модуль — это уровень совместимости, предоставляемый для упрощения переноса существующего кода, который относится к функциям среды выполнения C в глобальном пространстве имен. Если вы хотите избежать добавления имен в глобальное пространство имен, используйте import std;. Если вам нужно упростить миграцию базы кода, использующую множество неквалифицированных (глобальное пространство имен) функций среды выполнения C, используйте import std.compat;. Это предоставляет имена среды выполнения на языке C из глобального пространства имен, поэтому вам не нужно указывать их все с помощью std::. Если у вас нет существующего кода, использующего функции среды выполнения C глобального пространства имен, вам не нужно использовать import std.compat;. Если вы вызываете только несколько функций среды выполнения C в коде, возможно, лучше использовать import std; и квалифицировать несколько имен глобального пространства имен C, требующих этого, с помощью std::. Например: std::printf(). Если при попытке компиляции кода возникает ошибка error C3861: 'printf': identifier not found, используйте import std.compat;, чтобы импортировать функции среды выполнения C из глобального пространства имен.

Пример: Как создать и импортировать std.compat

Прежде чем использовать import std.compat; , необходимо скомпилировать файл интерфейса модуля, найденный в форме std.compat.ixxисходного кода. Visual Studio поставляет исходный код модуля, чтобы можно было скомпилировать модуль с помощью параметров компилятора, соответствующих проекту. Действия аналогичны созданию именованного std модуля. Именованный std модуль создается сначала, так как std.compat зависит от него:

  1. Откройте командную строку Native Tools для VS: в меню "Пуск" Windows введите x86 native, и в списке приложений должна появиться командная строка. Убедитесь, что запрос предназначен для Visual Studio 2022 версии 17.5 или выше. Если вы используете неправильную версию запроса, вы получите ошибки компилятора.

  2. Создайте каталог, чтобы попробовать этот пример, например %USERPROFILE%\source\repos\STLModules, и сделать его текущим каталогом. Если вы выберете каталог, в который у вас нет доступа на запись, вы получите ошибки.

  3. Скомпилируйте именованные модули std и std.compat с помощью следующей команды:

    cl /std:c++latest /EHsc /nologo /W4 /c "%VCToolsInstallDir%\modules\std.ixx" "%VCToolsInstallDir%\modules\std.compat.ixx"
    

    Необходимо скомпилировать std и std.compat использовать те же параметры компилятора, которые вы планируете использовать с кодом, который будет импортировать их. Если у вас есть решение с несколькими проектами, их можно скомпилировать один раз, а затем ссылаться на них из всех проектов с помощью параметра компилятора /reference .

    Если возникают ошибки, убедитесь, что вы используете правильную версию командной строки.

    Компилятор выводит четыре файла из предыдущих двух шагов:

    • std.ifc — это именованный интерфейс модуля, скомпилированный в двоичный файл, к которому компилятор обращается, чтобы обработать оператор import std;. Компилятор также обращается к std.ifc, чтобы обработать import std.compat;, потому что std.compat основан на std. Это артефакт, существующий только на этапе компиляции. Он не поставляется с вашим приложением.
    • std.obj содержит реализацию стандартной библиотеки.
    • std.compat.ifc — это скомпилированный двоичный интерфейс именованного модуля, который компилятор использует для обработки оператора import std.compat;. Это артефакт, существующий только на этапе компиляции. Он не поставляется с вашим приложением.
    • std.compat.obj содержит реализацию. Основную часть реализации обеспечивает std.obj. Добавьте std.obj в командную строку при компиляции примера приложения для статического связывания функциональных возможностей, используемых из стандартной библиотеки в приложение.

    Вы можете управлять именем файла объекта и именем именованного файла интерфейса модуля с помощью следующих переключателей:

    • /Fo задает имя файла объекта. Например: /Fo:"somethingelse". По умолчанию компилятор использует то же имя для файла объекта, что и исходный файл модуля (.ixx). В этом примере имена файлов объектов и по умолчанию являются std.obj и std.compat.obj по умолчанию, так как компилируем файлы std.ixx модуля и std.compat.ixx.
    • /ifcOutput задает имя именованного файла интерфейса модуля (.ifc). Например: /ifcOutput "somethingelse.ifc". По умолчанию компилятор использует то же имя для файла интерфейса модуля (.ifc), что и исходный файл модуля (.ixx). В этом примере создаются файлы ifc, которые по умолчанию названы std.ifc и std.compat.ifc, поскольку мы компилируем файлы модуля std.ixx и std.compat.ixx.
  4. Импортируйте библиотеку std.compat , сначала создав файл с именем stdCompatExample.cpp со следующим содержимым:

    import std.compat;
    
    int main()
    {
        printf("Import std.compat to get global names like printf()\n");
    
        std::vector<int> v{5, 5, 5};
        for (const auto& e : v)
        {
            printf("%i", e);
        }
    }
    

    В приведенном выше коде import std.compat; заменяет #include <cstdio> и #include <vector>. import std.compat; Инструкция делает стандартную библиотеку и функции среды выполнения C доступными с помощью одной инструкции. Импорт этого именованного модуля, который включает стандартную библиотеку C++ и функции глобального пространства имен среды выполнения C, выполняется быстрее, чем обработка одного #include , например #include <vector>.

  5. Скомпилируйте пример с помощью следующей команды:

    cl /std:c++latest /EHsc /nologo /W4 stdCompatExample.cpp
    link stdCompatExample.obj std.obj std.compat.obj
    

    Нам не нужно указывать std.compat.ifc в командной строке, так как компилятор автоматически ищет .ifc файл, соответствующий имени модуля в инструкции import . Когда компилятор обнаруживает import std.compat;, он находит std.compat.ifc, потому что мы поместили его в тот же каталог, что и исходный код, избавляя нас от необходимости указывать его в командной строке. .ifc Если файл находится в каталоге, отличном от исходного кода или имеет другое имя, используйте /reference переключатель компилятора для ссылки на него.

    При импорте std.compatнеобходимо связаться с обоими std.compat и std.obj потому, что std.compat использует код в std.obj.

    Если вы создаете один проект, вы можете объединить шаги по созданию std именованных модулей и std.compat стандартной библиотеки, добавив "%VCToolsInstallDir%\modules\std.ixx" и "%VCToolsInstallDir%\modules\std.compat.ixx" (в этом порядке) в командную строку. В этом руководстве показано создание модулей стандартной библиотеки как отдельный шаг, так как необходимо создать только стандартные именованные модули библиотеки один раз, а затем можно ссылаться на них из проекта или из нескольких проектов. Но если их удобно создавать одновременно, обязательно поместите их перед любыми .cpp файлами, которые их используют, и укажите /Fe для присвоения имени встроенному exe как показано в этом примере.

    cl /c /FestdCompatExample /std:c++latest /EHsc /nologo /W4 "%VCToolsInstallDir%\modules\std.ixx" "%VCToolsInstallDir%\modules\std.compat.ixx" stdCompatExample.cpp
    link stdCompatExample.obj std.obj std.compat.obj
    

    В этом примере компиляция исходного кода и связывание реализации модуля с приложением являются отдельными шагами. Они не должны быть. Вы можете использовать cl /std:c++latest /EHsc /nologo /W4 stdCompatExample.cpp std.obj std.compat.obj для компиляции и связывания на одном шаге. Однако может быть удобно сначала проводить сборку и связывание отдельно, так как библиотеки стандартных модулей требуется создать лишь один раз. После этого вы можете ссылаться на них из вашего проекта или из нескольких проектов на этапе связывания в процессе сборки.

    Предыдущая команда компилятора создает исполняемый файл с именем stdCompatExample.exe. При запуске он создает следующие выходные данные:

    Import std.compat to get global names like printf()
    555
    

Рекомендации по использованию стандартной библиотеки с именем модуля

Версионирование именованных модулей такое же, как для заголовков. Файлы модуля .ixx, устанавливаются вместе с заголовками, например, "%VCToolsInstallDir%\modules\std.ixx", который развёртывается в C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Tools\MSVC\14.38.33130\modules\std.ixx в указанную версию инструментов, используемых на момент написания. Выберите версию именованного модуля так же, как вы выбираете версию файла заголовка для использования — по каталогу, с которого вы на них ссылаетесь.

Не смешивайте импорты блоков заголовков и именованных модулей. Например, не используйте import <vector>; и import std; в одном и том же файле.

Не смешивайте и не сопоставляйте файлы заголовков стандартной библиотеки C++ и именованные модули std или std.compat. Например, не #include <vector>, import std; в одном файле. Однако вы можете включить заголовки C и импортировать именованные модули в один и тот же файл. Например, можно import std; и #include <math.h> в одном файле. Просто не включайте стандартную версию <cmath>библиотеки C++ .

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

Если необходимо использовать assert() макрос, то #include <assert.h>.

Если необходимо использовать errno макрос, #include <errno.h>. Так как именованные модули не предоставляют макросы, это обходное решение, если необходимо, например, проверить наличие ошибок из <math.h>.

Макросы, такие как NAN, INFINITY и INT_MIN, определяются с помощью <limits.h>, которые вы можете включить. Однако, если вы import std;, вы можете использовать std::numeric_limits<double>::quiet_NaN() и std::numeric_limits<double>::infinity() вместо NAN и INFINITY, а std::numeric_limits<int>::min() вместо INT_MIN.

Сводка

В этом руководстве вы импортировали стандартную библиотеку с помощью модулей. Далее вы узнаете о создании и импорте собственных модулей в учебнике по именованным модулям в C++.

См. также

Сравнение единиц заголовков, модулей и предварительно скомпилированных заголовков
Общие сведения о модулях в C++
Обзор модулей C++ в Visual Studio
Перемещение проекта на модули C++ под названием Modules