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


Обзор модулей в 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:
exportopt module-partitionoptattribute-specifier-seqmodule module-name ;

module-import-declaration:
exportopt opt opt import module-name attribute-specifier-seq ;
exportopt opt opt import module-partition attribute-specifier-seq ;
exportopt opt opt import 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, , importexport
Руководство по именованным модулям
Сравнение единиц заголовков, модулей и предварительно скомпилированных заголовков