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


Обзор модулей в C++

В C++20 представлены модули . Модуль — это набор файлов исходного кода, скомпилированных независимо от исходных файлов (или, более точно, от единиц перевода , которые их импортируют).

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

Модули можно использовать параллельно с файлами заголовков. Исходный файл C++ может содержать import модули, а также #include файлы заголовков. В некоторых случаях файл заголовка можно импортировать в виде модуля, который быстрее, чем использование #include для обработки с помощью препроцессора. Рекомендуем по возможности использовать модули в новых проектах, а не файлы заголовков. Для более крупных существующих проектов при активной разработке поэкспериментируйте с преобразованием устаревших заголовков в модули. Основывайте внедрение на том, достигается ли значительное сокращение времени компиляции.

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

Начиная с Visual Studio 2022 версии 17.5 импорт стандартной библиотеки в качестве модуля является стандартизованным и полностью реализован в компиляторе Microsoft C++. Сведения о импорте стандартной библиотеки с помощью модулей см. в статье Импорт стандартной библиотеки C++ с помощью модулей.

Модули с одним разделом

Модуль с одним разделом — это модуль, состоящий из одного исходного файла. Интерфейс модуля и реализация находятся в одном файле.

В следующем примере модуля с одним разделом показан простое определение модуля в исходном файле с именем Example.ixx. Расширение .ixx — это расширение по умолчанию для файлов интерфейса модуля в Visual Studio. Если вы хотите использовать другое расширение, используйте параметр /interface для компиляции в виде интерфейса модуля. В этом примере файл интерфейса содержит определение функции и объявление. Вы также можете поместить определения в один или несколько отдельных файлов реализации модуля, как показано в следующем примере, но это пример односекционного модуля.

Оператор export module Example; указывает, что этот файл является основным интерфейсом для модуля с именем Example. Модификатор export перед int 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 std;
import Example;

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 выбратьmodulemodule-namemodule-partitionвыбратьattribute-specifier-seqвыбрать;

module-import-declaration:
export выбратьimportmodule-nameattribute-specifier-seqвыбрать;
export выбратьimportmodule-partitionattribute-specifier-seqвыбрать;
export выбратьimportheader-nameattribute-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;
import MyModuleB;

//... rest of file

Для управления импортом модулей можно использовать традиционный файл заголовка:

// MyProgram.h
#ifdef C_RUNTIME_GLOBALS
import std.compat;
#else
import std;
#endif

Импортированные файлы заголовков

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

import <vector>;
import "myheader.h";

См. также

Импорт стандартной библиотеки C++ с помощью модулей
module, import, export
Руководство по именованным модулям
Сравнение единиц заголовков, модулей и предварительно скомпилированных заголовков