Namespaces
Variants

Modules (since C++20)

From cppreference.net
C++ language
General topics
Flow control
Conditional execution statements
Iteration statements (loops)
Jump statements
Functions
Function declaration
Lambda function expression
inline specifier
Dynamic exception specifications ( until C++17* )
noexcept specifier (C++11)
Exceptions
Namespaces
Types
Specifiers
constexpr (C++11)
consteval (C++20)
constinit (C++20)
Storage duration specifiers
Initialization
Expressions
Alternative representations
Literals
Boolean - Integer - Floating-point
Character - String - nullptr (C++11)
User-defined (C++11)
Utilities
Attributes (C++11)
Types
typedef declaration
Type alias declaration (C++11)
Casts
Memory allocation
Classes
Class-specific function properties
Special member functions
Templates
Miscellaneous

대부분의 C++ 프로젝트는 여러 개의 번역 단위를 사용하므로 이러한 단위들 간에 선언 정의 를 공유해야 합니다. 이를 위해 헤더 사용이 두드러지는데, 그 예로 표준 라이브러리 가 있으며, 해당 선언들은 해당 헤더를 포함시킴 으로써 제공될 수 있습니다.

모듈은 번역 단위 간에 선언과 정의를 공유하기 위한 언어 기능입니다. 이는 헤더의 일부 사용 사례에 대한 대안입니다.

모듈은 namespaces 와 직교 관계에 있습니다.

// helloworld.cpp
export module helloworld; // 모듈 선언
import <iostream>;        // 임포트 선언
export void hello()       // 익스포트 선언
{
    std::cout << "Hello world!\n";
}
// main.cpp
import helloworld; // import 선언
int main()
{
    hello();
}

목차

구문

export (선택 사항) module module-name module-partition  (선택 사항) attr  (선택 사항) ; (1)
export declaration (2)
export { declaration-seq  (선택 사항) } (3)
export (선택 사항) import module-name attr  (선택 사항) ; (4)
export (선택 사항) import module-partition attr  (선택 사항) ; (5)
export (선택 사항) import header-name attr  (선택 사항) ; (6)
module; (7)
module : private; (8)
1) 모듈 선언. 현재 번역 단위가 모듈 유닛 임을 선언합니다.
2,3) 내보내기 선언. declaration 또는 declaration-seq 에 있는 모든 네임스페이스 범위 선언들을 내보냅니다.
4,5,6) 임포트 선언. 모듈 유닛/모듈 파티션/헤더 유닛을 임포트합니다.
7) 전역 모듈 조각 을 시작합니다.
8) private module fragment 를 시작합니다.

모듈 선언

번역 단위는 모듈 선언을 가질 수 있으며, 이 경우 모듈 단위 로 간주됩니다. 모듈 선언 이 제공되는 경우, (나중에 다룰 전역 모듈 조각 을 제외하고) 번역 단위의 첫 번째 선언이어야 합니다. 각 모듈 단위는 모듈 선언에 제공된 모듈 이름 (및 선택적으로 파티션)과 연관됩니다.

export (선택 사항) module module-name module-partition  (선택 사항) attr  (선택 사항) ;

모듈 이름은 점으로 구분된 하나 이상의 식별자로 구성됩니다(예: mymodule , mymodule.mysubmodule , mymodule2 ...). 점 자체는 내재적인 의미를 갖지 않지만, 비공식적으로 계층 구조를 표현하는 데 사용됩니다.

모듈 이름이나 모듈 파티션에 있는 식별자가 객체형 매크로 로 정의된 경우, 프로그램은 올바르지 않습니다.

명명된 모듈(named module) 은 동일한 모듈 이름을 가진 모듈 유닛들의 집합입니다.

선언에 export 키워드가 포함된 모듈 유닛은 모듈 인터페이스 유닛 이라고 하며, 다른 모든 모듈 유닛은 모듈 구현 유닛 이라고 합니다.

모든 명명된 모듈에 대해 모듈 파티션을 지정하지 않는 모듈 인터페이스 유닛이 정확히 하나 존재해야 합니다; 이 모듈 유닛은 기본 모듈 인터페이스 유닛 이라고 합니다. 해당 모듈을 임포트할 때 이 유닛의 내보낸 내용을 사용할 수 있습니다.

// (각 줄은 별도의 번역 단위를 나타냅니다)
export module A;   // 명명된 모듈 'A'에 대한 기본 모듈 인터페이스 단위를 선언합니다
module A;          // 명명된 모듈 'A'에 대한 모듈 구현 단위를 선언합니다
module A;          // 명명된 모듈 'A'에 대한 또 다른 모듈 구현 단위를 선언합니다
export module A.B; // 명명된 모듈 'A.B'에 대한 기본 모듈 인터페이스 단위를 선언합니다
module A.B;        // 명명된 모듈 'A.B'에 대한 모듈 구현 단위를 선언합니다

선언 및 정의 내보내기

모듈 인터페이스 유닛은 선언(정의 포함)을 내보낼 수 있으며, 이는 다른 번역 유닛에서 가져올 수 있습니다. 선언을 내보내려면 export 키워드를 접두사로 붙이거나, export 블록 안에 배치하십시오.

export 선언
export { 선언-시퀀스  (선택적) }
export module A; // 'A'라는 이름의 모듈에 대한 기본 모듈 인터페이스 유닛을 선언합니다
// hello()는 'A'를 임포트하는 번역 유닛에서 보일 것입니다
export char const* hello() { return "hello"; } 
// world()는 보이지 않을 것입니다
char const* world() { return "world"; }
// one()과 zero() 모두 보일 것입니다
export
{
    int one()  { return 1; }
    int zero() { return 0; }
}
// 네임스페이스 내보내기도 작동합니다: hi::english()와 hi::french()가 보일 것입니다
export namespace hi
{
    char const* english() { return "Hi!"; }
    char const* french()  { return "Salut!"; }
}

모듈 및 헤더 유닛 임포트

모듈은 import 선언 을 통해 가져옵니다:

export (선택 사항) import module-name attr  (선택 사항) ;

주어진 이름의 모듈 인터페이스 유닛에서 내보내진 모든 선언과 정의는 import 선언을 사용하는 번역 유닛에서 사용 가능하게 됩니다.

가져오기 선언은 모듈 인터페이스 단위에서 내보낼 수 있습니다. 즉, 모듈 B A 를 내보내기-가져오기하면, B 를 가져오는 것은 A 의 모든 내보내기도 보이게 만듭니다.

모듈 유닛에서 모든 import 선언(export-import 포함)은 모듈 선언 이후, 다른 모든 선언 이전에 그룹화되어야 합니다.

/////// A.cpp ('A'의 주 모듈 인터페이스 단위)
export module A;
export char const* hello() { return "hello"; }
/////// B.cpp ('B'의 주 모듈 인터페이스 단위)
export module B;
export import A;
export char const* world() { return "world"; }
/////// main.cpp (모듈 단위가 아님)
#include <iostream>
import B;
int main()
{
    std::cout << hello() << ' ' << world() << '\n';
}

#include 는 모듈 유닛( global module fragment 외부)에서 사용되어서는 안 됩니다. 포함된 모든 선언과 정의가 모듈의 일부로 간주되기 때문입니다. 대신, 헤더는 import declaration 을 사용하여 header units 로도 가져올 수 있습니다:

export (선택사항) import 헤더-이름 속성  (선택사항) ;

헤더 유닛은 헤더로부터 합성된 별도의 번역 유닛입니다. 헤더 유닛을 임포트하면 해당 헤더의 모든 정의와 선언에 접근할 수 있습니다. 전처리기 매크로 또한 접근 가능합니다 (임포트 선언이 전처리기에 의해 인식되기 때문입니다).

그러나 #include 와는 반대로, import 선언 시점에 이미 정의된 전처리 매크로는 헤더의 처리에 영향을 미치지 않습니다. 이는 일부 경우(일부 헤더는 설정 형태로 전처리 매크로를 사용함)에 불편할 수 있으며, 이러한 경우 global module fragment 의 사용이 필요합니다.

/////// A.cpp ('A'의 기본 모듈 인터페이스 유닛)
export module A;
import <iostream>;
export import <string_view>;
export void print(std::string_view message)
{
    std::cout << message << std::endl;
}
/////// main.cpp (모듈 유닛이 아님)
import A;
int main()
{
    std::string_view message = "Hello, world!";
    print(message);
}

전역 모듈 조각

모듈 유닛은 전역 모듈 조각(global module fragment) 으로 시작할 수 있으며, 이는 헤더를 임포트하는 것이 불가능한 경우(특히 헤더가 설정을 위해 전처리기 매크로를 사용하는 경우) 헤더를 포함하는 데 사용될 수 있습니다.

module;

전처리 지시문  (선택적)

모듈 선언

모듈 유닛에 전역 모듈 조각이 있는 경우, 첫 번째 선언은 반드시 module; 이어야 합니다. 그런 다음 전역 모듈 조각에는 전처리 지시문 만 나타날 수 있습니다. 이후 표준 모듈 선언이 전역 모듈 조각의 끝과 모듈 내용의 시작을 표시합니다.

/////// A.cpp ('A'의 기본 모듈 인터페이스 유닛)
module;
// _POSIX_C_SOURCE를 정의하면 POSIX 표준에 따라
// 표준 헤더에 함수들이 추가됩니다.
#define _POSIX_C_SOURCE 200809L
#include <stdlib.h>
export module A;
import <ctime>;
// 데모용으로만 사용 (약한 난수 생성 소스).
// 대신 C++ <random>을 사용하세요.
export double weak_random()
{
    std::timespec ts;
    std::timespec_get(&ts, TIME_UTC); // <ctime>에서
    // POSIX 표준에 따라 <stdlib.h>에서 제공됨.
    srand48(ts.tv_nsec);
    // drand48()은 0과 1 사이의 난수를 반환합니다.
    return drand48();
}
/////// main.cpp (모듈 유닛이 아님)
import <iostream>;
import A;
int main()
{
    std::cout << "0과 1 사이의 난수 값: " << weak_random() << '\n';
}

비공개 모듈 조각

기본 모듈 인터페이스 유닛은 private module fragment 로 끝날 수 있으며, 이를 통해 모듈을 단일 번역 유닛으로 표현하면서도 모듈의 모든 내용을 임포트하는 측에서 접근할 수 없도록 할 수 있습니다.

module : private;

선언-시퀀스  (선택적)

비공개 모듈 조각 은 다른 번역 단위의 동작에 영향을 줄 수 있는 모듈 인터페이스 단위의 부분을 종료합니다. 모듈 단위가 비공개 모듈 조각 을 포함하는 경우, 해당 모듈의 유일한 모듈 단위가 됩니다.

export module foo;
export int f();
module : private; // 다른 번역 단위의 동작에 영향을 줄 수 있는
                  // 모듈 인터페이스 단위 부분을 종료합니다
                  // 비공개 모듈 조각을 시작합니다
int f()           // foo를 임포트하는 곳에서 접근할 수 없는 정의
{
    return 42;
}

모듈 파티션

모듈은 모듈 파티션 유닛(module partition units) 을 가질 수 있습니다. 이들은 모듈 선언에 모듈 파티션을 포함하는 모듈 유닛으로, 모듈 이름 뒤에 콜론 : 으로 시작하여 배치됩니다.

export module A:B; // 모듈 'A'의 파티션 ':B'에 대한 모듈 인터페이스 유닛을 선언합니다.

모듈 파티션은 정확히 하나의 모듈 유닛을 나타냅니다(두 개의 모듈 유닛이 동일한 모듈 파티션을 지정할 수 없습니다). 이들은 명명된 모듈 내부에서만 보입니다(명명된 모듈 외부의 번역 유닛은 모듈 파티션을 직접 임포트할 수 없습니다).

모듈 파티션은 동일한 이름의 모듈을 가진 모듈 유닛에서 임포트될 수 있습니다.

export (선택 사항) import module-partition attr  (선택 사항) ;
/////// 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되어야 하며, 해당 모듈이 import될 때 이들의 export된 문장들이 보이게 됩니다.

export (선택 사항) import module-partition attr  (선택 사항) ;
///////  A.cpp   
export module A;     // 기본 모듈 인터페이스 유닛
export import :B;    // Hello()는 'A'를 임포트할 때 보입니다.
import :C;           // WorldImpl()은 이제 'A.cpp'에서만 보입니다.
// export import :C; // 오류: 모듈 구현 유닛을 내보낼 수 없습니다.
// World()는 'A'를 임포트하는 모든 번역 유닛에서 보입니다.
export char const* World()
{
    return WorldImpl();
}
/////// A-B.cpp 
export module A:B; // 파티션 모듈 인터페이스 유닛
// Hello()는 'A'를 임포트하는 모든 번역 유닛에서 볼 수 있습니다.
export char const* Hello() { return "Hello"; }
/////// A-C.cpp 
module A:C; // 파티션 모듈 구현 유닛
// WorldImpl()은 ':C'를 임포트하는 'A'의 모든 모듈 유닛에서 볼 수 있습니다.
char const* WorldImpl() { return "World"; }
/////// main.cpp 
import A;
import <iostream>;
int main()
{
    std::cout << Hello() << ' ' << World() << '\n';
    // WorldImpl(); // 오류: WorldImpl()이 보이지 않습니다.
}

모듈 소유권

일반적으로, 선언이 모듈 단위에서 모듈 선언 뒤에 나타나면, 해당 선언은 그 모듈에 부착됩니다 .

엔터티의 선언이 명명된 모듈에 첨부된 경우, 해당 엔터티는 해당 모듈 내에서만 정의될 수 있습니다. 이러한 엔터티의 모든 선언은 동일한 모듈에 첨부되어야 합니다.

선언이 명명된 모듈에 첨부되고 내보내지지 않으면, 선언된 이름은 모듈 링크age 를 가집니다.

export module lib_A;
int f() { return 0; } // f는 모듈 링크를 가짐
export int x = f();   // x는 0과 같음
export module lib_B;
int f() { return 1; } // OK, lib_A의 f와 lib_B의 f는 서로 다른 개체를 참조합니다
export int y = f(); // y는 1입니다

만약 동일한 엔티티의 두 선언 이 서로 다른 모듈에 부착된 경우, 프로그램은 형식에 맞지 않습니다(ill-formed); 둘 중 어느 것도 다른 하나로부터 도달 가능하지 않은 경우 진단은 필요하지 않습니다.

/////// decls.h
int f(); // #1, 전역 모듈에 연결됨
int g(); // #2, 전역 모듈에 연결됨
/////// M 모듈의 인터페이스
module;
#include "decls.h"
export module M;
export using ::f; // OK, 엔티티를 선언하지 않으며 #1을 내보냄
int g();          // 오류: #2와 일치하지만 M에 연결됨
export int h();   // #3
export int k();   // #4
/////// 다른 번역 단위
import M;
static int h();   // 오류: #3과 일치함
int k();          // 오류: #4와 일치함

다음 선언들은 명명된 모듈에 속하지 않습니다 (따라서 선언된 개체는 모듈 외부에서 정의될 수 있습니다):

export module lib_A;
namespace ns // ns는 lib_A에 부착되지 않음
{
    export extern "C++" int f(); // f는 lib_A에 부착되지 않음
           extern "C++" int g(); // g는 lib_A에 부착되지 않음
    export              int h(); // h는 lib_A에 부착됨
}
// ns::h는 lib_A에서 정의되어야 하지만, ns::f와 ns::g는 다른 곳(예: 기존 소스 파일)에서 정의될 수 있음

참고 사항

기능 테스트 매크로 표준 기능
__cpp_modules 201907L (C++20) 모듈 — 핵심 언어 지원
__cpp_lib_modules 202207L (C++23) 표준 라이브러리 모듈 std std. compat

키워드

private , module , import , export

결함 보고서

다음의 동작 변경 결함 보고서들은 이전에 발표된 C++ 표준에 소급 적용되었습니다.

DR 적용 대상 게시된 동작 올바른 동작
CWG 2732 C++20 importable 헤더가 import 지점의
전처리기 상태에 반응할 수 있는지 불명확했음
반응하지 않음
P3034R1 C++20 모듈 이름과 모듈 파티션이 객체형 매크로로
정의된 식별자를 포함할 수 있었음
금지됨