Spec-Zone.ru › C++

Модули (с C++20)

Большинство проектов на C++ используют несколько трансляционных единиц и, следовательно, нуждаются в совместном использовании объявлений и определений между этими единицами. Для этой цели широко используются заголовочные файлы, примером чего является стандартная библиотека, чьи объявления могут быть предоставлены путём включения соответствующего заголовочного файла.

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

Модули ортогональны пространствам имён.

// helloworld.cpp
export module helloworld; // module declaration
 
import <iostream>;        // import declaration
 
export void hello()       // export declaration
{
    std::cout << "Hello world!\n";
}
// main.cpp
import helloworld; // import declaration
 
int main()
{
    hello();
}

Синтаксис

export(необязательно) module имя-модуля разбиение-модуля (необязательно) атрибут (необязательно) ; (1)
export объявление (2)
export { последовательность-объявлений (необязательно) } (3)
export(необязательно) import имя-модуля атрибут (необязательно) ; (4)
export(необязательно) import разбиение-модуля атрибут (необязательно) ; (5)
export(необязательно) import имя-заголовочного-файла атрибут (необязательно) ; (6)
module; (7)
module : private; (8)
1) Объявление модуля. Объявляет, что текущая трансляционная единица является единицей модуля.
2,3) Объявление экспорта. Экспортирует все объявления в области имён объявление или последовательность-объявлений.
4,5,6) Объявление импорта. Импортирует единицу модуля/разбиение модуля/единицу заголовочного файла.
7) Начало фрагмента глобального модуля.
8) Начало фрагмента частного модуля.

Объявления модулей

В трансляционной единице может быть объявление модуля, в этом случае она считается единицей модуля. Объявление модуля, если предоставлено, должно быть первым объявлением трансляционной единицы (за исключением фрагмента глобального модуля, который рассматривается позже). Каждая единица модуля ассоциируется с именем модуля (и необязательно с разбиением), указанным в объявлении модуля.

export(необязательно) module имя-модуля разбиение-модуля (необязательно) атрибут (необязательно) ;

Имя модуля состоит из одного или нескольких идентификаторов, разделенных точками (например: mymodule, mymodule.mysubmodule, mymodule2...). Точки не имеют внутреннего смысла, однако используются неформально для представления иерархии.

Именованный модуль — это набор единиц модулей с одинаковым именем модуля.

Единицы модулей, в объявлении которых присутствует ключевое слово export , называются единицами интерфейса модуля; все остальные единицы модулей называются единицами реализации модуля.

Для каждого именованного модуля должна быть ровно одна единица интерфейса модуля, которая не указывает разбиение модуля; эта единица модуля называется первичной единицей интерфейса модуля. Её экспортируемое содержимое будет доступно при импорте соответствующего именованного модуля.

// (each line represents a separate translation unit)
 
export module A;   // declares the primary module interface unit for named module 'A'
module A;          // declares a module implementation unit for named module 'A'
module A;          // declares another module implementation unit for named module 'A'
export module A.B; // declares the primary module interface unit for named module 'A.B'
module A.B;        // declares a module implementation unit for named module 'A.B'

Экспорт объявлений и определений

Единицы интерфейса модуля могут экспортировать объявления (включая определения), которые могут быть импортированы другими трансляционными единицами. Чтобы экспортировать объявление, либо добавьте перед ним ключевое слово export, либо поместите его внутри блока export.

export объявление
export { последовательность-объявлений (необязательно) }
export module A; // declares the primary module interface unit for named module 'A'
 
// hello() will be visible by translations units importing 'A'
export char const* hello() { return "hello"; } 
 
// world() will NOT be visible.
char const* world() { return "world"; }
 
// Both one() and zero() will be visible.
export
{
    int one()  { return 1; }
    int zero() { return 0; }
}
 
// Exporting namespaces also works: hi::english() and hi::french() will be visible.
export namespace hi
{
    char const* english() { return "Hi!"; }
    char const* french()  { return "Salut!"; }
}

Импортирование модулей и заголовочных файлов

Модули импортируются через объявление импорта:

export(необязательно) import имя-модуля атрибут (необязательно) ;

Все объявления и определения, экспортированные в единицах интерфейса модуля данного именованного модуля, будут доступны в трансляционной единице, использующей объявление импорта.

Объявления импорта могут быть экспортированы в единице интерфейса модуля. То есть, если модуль B экспортно импортирует A, то импорт B также сделает видимыми все экспорты из A.

Во всех единицах модуля все объявления импорта (включая экспортно-импорты) должны быть сгруппированы после объявления модуля и перед всеми другими объявлениями.

/////// A.cpp (primary module interface unit of 'A')
export module A;
 
export char const* hello() { return "hello"; }
 
/////// B.cpp (primary module interface unit of 'B')
export module B;
 
export import A;
 
export char const* world() { return "world"; }
 
/////// main.cpp (not a module unit)
#include <iostream>
import B;
 
int main()
{
    std::cout << hello() << ' ' << world() << '\n';
}

#include не следует использовать в единице модуля (вне фрагмента глобального модуля), так как все включенные объявления и определения считались бы частью модуля. Вместо этого, заголовочные файлы также могут быть импортированы как единицы заголовочных файлов с объявлением импорта:

export(необязательно) import имя-заголовочного-файла атрибут (необязательно) ;

Единица заголовочного файла — это отдельная трансляционная единица, сгенерированная из заголовочного файла. Импорт единицы заголовочного файла сделает доступными все его определения и объявления. Препроцессорные макросы также доступны (потому что объявления импорта распознаются препроцессором).

Однако, в отличие от #include, препроцессорные макросы, уже определённые к моменту объявления импорта, не повлияют на обработку заголовочного файла. Это может быть неудобно в некоторых случаях (некоторые заголовочные файлы используют препроцессорные макросы как форму конфигурации), в этом случае необходимо использование фрагмента глобального модуля.

/////// A.cpp (primary module interface unit of 'A')
export module A;
 
import <iostream>;
export import <string_view>;
 
export void print(std::string_view message)
{
    std::cout << message << std::endl;
}
 
/////// main.cpp (not a module unit)
import A;
 
int main()
{
    std::string_view message = "Hello, world!";
    print(message);
}

Фрагмент глобального модуля

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

module;

препроцессорные-директивы (необязательно)

объявление-модуля

Если единица модуля имеет фрагмент глобального модуля, то её первое объявление должно быть module;. Затем, в фрагменте глобального модуля могут присутствовать только препроцессорные директивы. Затем стандартное объявление модуля отмечает конец фрагмента глобального модуля и начало содержимого модуля.

/////// A.cpp (primary module interface unit of 'A')
module;
 
// Defining _POSIX_C_SOURCE adds functions to standard headers,
// according to the POSIX standard.
#define _POSIX_C_SOURCE 200809L
#include <stdlib.h>
 
export module A;
 
import <ctime>;
 
// Only for demonstration (bad source of randomness).
// Use C++ <random> instead.
export double weak_random()
{
    std::timespec ts;
    std::timespec_get(&ts, TIME_UTC); // from <ctime>
 
    // Provided in <stdlib.h> according to the POSIX standard.
    srand48(ts.tv_nsec);
 
    // drand48() returns a random number between 0 and 1.
    return drand48();
}
 
/////// main.cpp (not a module unit)
import <iostream>;
import A;
 
int main()
{
    std::cout << "Random value between 0 and 1: " << weak_random() << '\n';
}

Фрагмент частного модуля

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

module : private;

последовательность-объявлений (необязательно)

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

export module foo;
 
export int f();
 
module : private; // ends the portion of the module interface unit that
                  // can affect the behavior of other translation units
                  // starts a private module fragment
 
int f()           // definition not reachable from importers of foo
{
    return 42;
}

Разбиения модулей

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

export module A:B; // Declares a module interface unit for module 'A', partition ':B'.

Разбиение модуля представляет собой ровно одну единицу модуля (две единицы модулей не могут обозначать одно и то же разбиение модуля). Они видимы только изнутри именованного модуля (трансляционные единицы вне именованного модуля не могут напрямую импортировать разбиение модуля).

Разбиение модуля может быть импортировано единицами модулей того же именованного модуля.

export(необязательно) import разбиение-модуля атрибут (необязательно) ;
/////// A-B.cpp   
export module A:B;
...
 
/////// A-C.cpp
module A:C;
...
 
/////// A.cpp
export module A;
 
import :C;
export import :B;
 
...

Все определения и объявления в модульном разделе видны импортирующему модульному блоку, экспортируются они или нет.

Модульные разделы могут быть модульными интерфейсными блоками (когда их модульные объявления имеют export). Они должны быть экспортно-импортированы основным модульным интерфейсным блоком, и их экспортированные операторы будут видны при импорте модуля.

export(необязательно) import атрибут-модульный-раздел (необязательно) ;
///////  A.cpp   
export module A;     // primary module interface unit
 
export import :B;    // Hello() is visible when importing 'A'.
import :C;           // WorldImpl() is now visible only for 'A.cpp'.
// export import :C; // ERROR: Cannot export a module implementation unit.
 
// World() is visible by any translation unit importing 'A'.
export char const* World()
{
    return WorldImpl();
}
/////// A-B.cpp 
export module A:B; // partition module interface unit
 
// Hello() is visible by any translation unit importing 'A'.
export char const* Hello() { return "Hello"; }
/////// A-C.cpp 
module A:C; // partition module implementation unit
 
// WorldImpl() is visible by any module unit of 'A' importing ':C'.
char const* WorldImpl() { return "World"; }
/////// main.cpp 
import A;
import <iostream>;
 
int main()
{
    std::cout << Hello() << ' ' << World() << '\n';
    // WorldImpl(); // ERROR: WorldImpl() is not visible.
}

Модульное владение

В общем случае, если объявление появляется после объявления модуля в модульном блоке, оно прикреплено к этому модулю.

Если объявление сущности прикреплено к именованному модулю, эта сущность может быть определена только в этом модуле. Все объявления такой сущности должны быть прикреплены к одному и тому же модулю.

Если объявление прикреплено к именованному модулю и не экспортировано, объявленное имя имеет связь модуля.

export module lib_A;
 
int f() { return 0; } // f has module linkage
export int x = f();   // x equals 0
export module lib_B;
 
int f() { return 1; } // OK, f in lib_A and f in lib_B refer to different entities
export int y = f(); // y equals 1

Если два соответствующих объявления прикреплены к разным модулям, и оба объявляют имена с внешней связью, программа является некорректной; диагностика не требуется, если ни одно из них не доступно из другого. На практике существуют две модели:

  • В модели слабого владения модулем такие объявления считаются объявлениями одной и той же сущности.
  • В модели сильного владения модулем они считаются объявлениями разных сущностей.
export module lib_A;
 
export constexpr int f() { return 0; } // f has external linkage
export module lib_B;
 
export constexpr int f() { return 1; }
// In the weak ownership model: multiple definitions of f; the linker may pick either
// In the strong ownership model: OK, f in lib_A and f in lib_B are different entities

Следующие объявления не прикреплены ни к одному именованному модулю (и, следовательно, объявленная сущность может быть определена вне модуля):

  • определения пространств имён с внешней связью;
  • объявления в спецификации связи языка.
export module lib_A;
 
namespace ns // ns is not attached to lib_A.
{
    export extern "C++" int f(); // f is not attached to lib_A.
           extern "C++" int g(); // g is not attached to lib_A.
    export              int h(); // h is attached to lib_A.
}
// ns::h must be defined in lib_A, but ns::f and ns::g can be defined elsewhere (e.g.
// in a traditional source file).

Примечания

Макрос проверки наличия функции Значение Std Функция
__cpp_modules 201907L (C++20) Модули — базовая поддержка языка
__cpp_lib_modules 202207L (C++23) Модули стандартной библиотеки std и std.compat

Ключевые слова

export, import, module, private

Отчёты об ошибках

Следующие отчёты об ошибках, меняющие поведение, были применены ретроактивно к ранее опубликованным стандартам C++.

DR Применено к Поведение, опубликованное Корректное поведение
CWG 2732 C++20 было неясно, могут ли импортируемые заголовки реагировать на состояние препроцессора с точки зрения импорта нет реакции

© cppreference.com
Licensed under the Creative Commons Attribution-ShareAlike Unported License v3.0.
https://en.cppreference.com/w/cpp/language/modules

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API