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

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

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

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

Необходимые компоненты

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

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

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

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

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

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

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

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

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

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

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

Пример. Создание и импорт std

  1. Откройте командную строку собственных средств x86 для VS: в меню "Пуск" Windows введите собственный код x86, а в списке приложений должен появиться запрос. Убедитесь, что запрос предназначен для 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 в командную строку при компиляции примера приложения для статического связывания функциональных возможностей, используемых из стандартной библиотеки в приложение.

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

    Коммутатор Значение
    /std:c++:latest Используйте последнюю версию стандарта языка C++ и библиотеки. Хотя поддержка модулей доступна в разделе /std:c++20, вам нужна последняя стандартная библиотека, чтобы получить поддержку стандартных именованных модулей библиотеки.
    /EHsc Используйте обработку исключений C++ за исключением функций, помеченных extern "C".
    /W4 Использование /W4 обычно рекомендуется, особенно для новых проектов, так как он включает все уровни 1, уровень 2, уровень 3 и большинство предупреждений уровня 4 (информационные), которые могут помочь вам поймать потенциальные проблемы рано. Он, по сути, предоставляет предупреждения, подобные lint, которые могут помочь обеспечить наименьшие возможные дефекты кода.
    /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. Откройте командную строку собственных средств для VS: в меню "Пуск" Windows введите собственный код x86 и в списке приложений должна появиться строка. Убедитесь, что запрос предназначен для 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 процессу, так как std.compat выполняет сборкуstdimport std.compat;. Это артефакт только во время компиляции. Он не отправляется с приложением.
    • 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.obj.
    • /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>, например.

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

Итоги

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

См. также

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