Обзор модулей в C++
В C++20 представлены модули. Модуль — это набор файлов исходного кода, скомпилированных независимо от исходных файлов (или более точно, единиц перевода, импортируемых ими).
Модули устраняют или сокращают многие проблемы, связанные с использованием файлов заголовков. Они часто сокращают время компиляции, иногда значительно. Макросы, директивы препроцессора и неэкспортированные имена, объявленные в модуле, не отображаются за пределами модуля. Они не влияют на компиляцию единицы перевода, импортируемой модулем. Модули можно импортировать в любом порядке без проблем с переопределениями макросов. Объявления в импортируемом модуле перевода не участвуют в разрешении перегрузки или поиске имен в импортированном модуле. После компиляции модуля результаты хранятся в двоичном файле, который описывает все экспортированные типы, функции и шаблоны. Компилятор может обрабатывать этот файл гораздо быстрее, чем файл заголовка. И компилятор может повторно использовать его везде, где модуль импортируется в проект.
Модули можно использовать параллельно с файлами заголовков. Исходный файл C++ может использовать import
модули и #include
файлы заголовков. В некоторых случаях можно импортировать файл заголовка в виде модуля, который быстрее, чем используется #include
для обработки с помощью препроцессора. Рекомендуется использовать модули в новых проектах, а не файлы заголовков как можно больше. Для более крупных существующих проектов при активной разработке поэкспериментируйте с преобразованием устаревших заголовков в модули. На основе внедрения зависит от того, получается ли существенное сокращение времени компиляции.
Чтобы контрастировать модули с другими способами импорта стандартной библиотеки, см. раздел "Сравнение единиц заголовков", модулей и предварительно скомпилированных заголовков.
Включение модулей в компиляторе Microsoft C++
Начиная с Visual Studio 2022 версии 17.1, стандартные модули C++20 полностью реализованы в компиляторе Microsoft C++.
Прежде чем он был указан стандартом C++20, корпорация Майкрософт поддерживала экспериментальную поддержку модулей. Компилятор также поддерживает импорт предварительно созданных модулей стандартной библиотеки, описанных ниже.
Начиная с Visual Studio 2022 версии 17.5 импорт стандартной библиотеки в качестве модуля является стандартизованным и полностью реализован в компиляторе Microsoft C++. В этом разделе описывается старый экспериментальный метод, который по-прежнему поддерживается. Сведения о новом стандартизованном способе импорта стандартной библиотеки с помощью модулей см. в статье "Импорт стандартной библиотеки C++ с помощью модулей".
Вы можете использовать функцию модулей для создания модулей с одним разделом и импорта модулей стандартной библиотеки, предоставляемых корпорацией Майкрософт. Чтобы включить поддержку модулей стандартной библиотеки, выполните компиляцию и /experimental:module
/std:c++latest
. В проекте Visual Studio щелкните правой кнопкой мыши узел проекта в Обозреватель решений и выберите "Свойства". Установите раскрывающийся список "Конфигурация" на все конфигурации, а затем выберите "Свойства>конфигурации C/C++>Language>Enable C++ Modules (экспериментальные)".
Модуль и код, который использует его, необходимо скомпилировать с теми же параметрами компилятора.
Использование стандартной библиотеки C++ в качестве модулей (экспериментальных)
В этом разделе описывается экспериментальная реализация, которая по-прежнему поддерживается. Новый стандартизованный способ использования стандартной библиотеки C++ в качестве модулей описывается в статье Импорт стандартной библиотеки C++ с помощью модулей.
Импортируя стандартную библиотеку C++ в виде модулей, а не включив ее в файлы заголовков, вы можете ускорить компиляцию в зависимости от размера проекта. Экспериментальная библиотека разделена на следующие именованные модули:
std.regex
предоставляет содержимое заголовка<regex>
std.filesystem
предоставляет содержимое заголовка<filesystem>
std.memory
предоставляет содержимое заголовка<memory>
std.threading
предоставляет содержимое заголовков<atomic>
, ,<condition_variable>
,<future>
,<mutex>
<shared_mutex>
и<thread>
std.core
предоставляет все остальное в стандартной библиотеке C++
Чтобы использовать эти модули, добавьте объявление импорта в начало файла исходного кода. Например:
import std.core;
import std.regex;
Чтобы использовать модули стандартной библиотеки Майкрософт, скомпилируйте программу с /EHsc
помощью параметров и /MD
параметров.
Пример
В следующем примере показано простое определение модуля в исходном файле Example.ixx
. Расширение .ixx
требуется для файлов интерфейса модуля в Visual Studio. В этом примере файл интерфейса содержит определение функции и объявление. Однако вы также можете поместить определения в один или несколько отдельных файлов реализации модуля, как показано в следующем примере.
Инструкция export module Example;
указывает, что этот файл является основным интерфейсом для вызываемого Example
модуля. Модификатор export
f()
указывает, что эта функция отображается при импорте Example
другой программы или модуля.
// Example.ixx
export module Example;
#define ANSWER 42
namespace Example_NS
{
int f_internal() {
return ANSWER;
}
export int f() {
return f_internal();
}
}
Файл MyProgram.cpp
используется import
для доступа к имени, экспортируемого Example
. Имя пространства имен Example_NS
отображается здесь, но не все его члены, так как они не экспортируются. Кроме того, макрос ANSWER
не отображается, так как макросы не экспортируются.
// MyProgram.cpp
import Example;
import std.core;
using namespace std;
int main()
{
cout << "The result of f() is " << Example_NS::f() << endl; // 42
// int i = Example_NS::f_internal(); // C2039
// int j = ANSWER; //C2065
}
Объявление import
может отображаться только в глобальной области.
Грамматика модуля
module-name
:
module-name-qualifier-seq
необ.identifier
module-name-qualifier-seq
:
identifier
.
module-name-qualifier-seq
identifier
.
module-partition
:
:
module-name
module-declaration
:
export
optmodule-partition
optattribute-specifier-seq
module
module-name
;
module-import-declaration
:
export
opt opt optimport
module-name
attribute-specifier-seq
;
export
opt opt optimport
module-partition
attribute-specifier-seq
;
export
opt opt optimport
header-name
attribute-specifier-seq
;
Реализация модулей
Интерфейс модуля экспортирует имя модуля и все пространства имен, типы, функции и т. д., составляющие общедоступный интерфейс модуля.
Реализация модуля определяет вещи, экспортированные модулем.
В самой простой форме модуль может быть одним файлом, который объединяет интерфейс модуля и реализацию. Вы также можете поместить реализацию в один или несколько отдельных файлов реализации модуля, как .h
и .cpp
файлы.
Для более крупных модулей можно разделить части модуля на подмодулы, называемые секциями. Каждая секция состоит из файла интерфейса модуля, который экспортирует имя секции модуля. У секции также может быть один или несколько файлов реализации секций. Модуль в целом имеет один основной интерфейс модуля, который является общедоступным интерфейсом модуля. При необходимости он может экспортировать интерфейсы секций.
Модуль состоит из одного или нескольких единиц модуля. Модуль — это единица перевода (исходный файл), содержащая объявление модуля. Существует несколько типов единиц модуля:
- Модуль интерфейса модуля экспортирует имя модуля или имя секции модуля. Модуль интерфейса модуля содержит
export module
в своем объявлении модуля. - Модуль реализации модуля не экспортирует имя модуля или имя секции модуля. Как подразумевает имя, он реализует модуль.
- Основной модуль интерфейса модуля экспортирует имя модуля. В модуле должен быть один и только один основной модуль интерфейса модуля.
- Единица интерфейса секционирования модуля экспортирует имя секции модуля.
- Модуль реализации секции модуля имеет имя секции модуля в объявлении модуля, но ключевое слово не
export
имеет ключевого слова.
Ключевое export
слово используется только в файлах интерфейса. Файл реализации может быть import
другим модулем, но он не export
может никаких имен. Файлы реализации могут иметь любое расширение.
Модули, пространства имен и поиск, зависящие от аргументов
Правила для пространств имен в модулях совпадают с любым другим кодом. Если объявление в пространстве имен экспортируется, то включающее пространство имен (за исключением элементов, которые не экспортируются явным образом в этом пространстве имен), также экспортируется неявно. Если пространство имен экспортируется явным образом, экспортируются все объявления в определении пространства имен.
Когда компилятор выполняет поиск в зависимости от аргументов для разрешения перегрузки в модуле импорта перевода, он рассматривает функции, объявленные в той же единице перевода (включая интерфейсы модулей), где определены тип аргументов функции.
Секции модулей
Раздел модуля аналогичен модулю, за исключением следующих:
- Он разделяет владение всеми объявлениями во всем модуле.
- Все имена, экспортированные файлами интерфейса секционирования, импортируются и экспортируются основным файлом интерфейса.
- Имя раздела должно начинаться с имени модуля, за которым следует двоеточие (
:
). - Объявления в любом из разделов отображаются во всем модуле.\
- Для предотвращения ошибок с одним определением (ODR) не требуются специальные меры предосторожности. Можно объявить имя (функция, класс и т. д.) в одной секции и определить его в другом.
Файл реализации секции начинается следующим образом и является внутренней секцией с точки зрения стандартов C++:
module Example:part1;
Файл интерфейса секции начинается следующим образом:
export module Example:part1;
Чтобы получить доступ к объявлениям в другой секции, необходимо импортировать его. Но он может использовать только имя секции, а не имя модуля:
module Example:part2;
import :part1;
Блок первичного интерфейса должен импортировать и повторно экспортировать все файлы секционирования интерфейса модуля, как показано ниже.
export import :part1;
export import :part2;
Основной блок интерфейса может импортировать файлы реализации секций, но не может экспортировать их. Эти файлы не разрешены для экспорта имен. Это ограничение позволяет модулю хранить сведения о реализации внутри модуля.
Модули и файлы заголовков
Файлы заголовков можно включить в исходный файл модуля, поставив директиву #include
перед объявлением модуля. Эти файлы считаются в глобальном фрагменте модуля. Модуль может видеть только имена в фрагменте глобального модуля, которые находятся в заголовках, которые он явно включает. Глобальный фрагмент модуля содержит только символы, используемые.
// MyModuleA.cpp
#include "customlib.h"
#include "anotherlib.h"
import std.core;
import MyModuleB;
//... rest of file
Для управления импортом модулей можно использовать традиционный файл заголовка:
// MyProgram.h
import std.core;
#ifdef DEBUG_LOGGING
import std.filesystem;
#endif
Импортированные файлы заголовков
Некоторые заголовки достаточно автономны, что их можно использовать с помощью ключевого import
слова. Основное различие между импортированным заголовком и импортированным модулем заключается в том, что все определения препроцессора в заголовке отображаются в программе импорта сразу после инструкции import
.
import <vector>;
import "myheader.h";
См. также
module
, , import
export
Руководство по именованным модулям
Сравнение единиц заголовков, модулей и предварительно скомпилированных заголовков