模組 (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 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) | ||||||||
[編輯] 模組宣告
一個翻譯單元可以有一個模組宣告,在這種情況下它被認為是模組單元。如果提供模組宣告,它必須是翻譯單元的第一個宣告(除了稍後介紹的全域性模組片段)。每個模組單元都與模組宣告中提供的模組名(以及可選的模組分割槽)相關聯。
export (可選) module module-name module-partition (可選) attr (可選) ; |
|||||||||
模組名由一個或多個用點分隔的識別符號組成(例如: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 declaration |
|||||||||
export { declaration-seq (可選) } |
|||||||||
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 module-name attr (可選) ; |
|||||||||
給定命名模組的模組介面單元中匯出的所有宣告和定義都將在使用匯入宣告的翻譯單元中可用。
匯入宣告可以在模組介面單元中匯出。也就是說,如果模組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 header-name attr (可選) ; |
|||||||||
頭單元是從標頭檔案合成的獨立翻譯單元。匯入頭單元將使其所有定義和宣告可訪問。預處理宏也可訪問(因為匯入宣告被預處理器識別)。
然而,與#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;
preprocessing-directives (可選) module-declaration |
|||||||||
如果模組單元有全域性模組片段,則其第一個宣告必須是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;
declaration-seq (可選) |
|||||||||
私有模組片段結束了模組介面單元中可能影響其他翻譯單元行為的部分。如果模組單元包含私有模組片段,它將是其模組的唯一模組單元。
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 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 module-partition attr (可選) ; |
|||||||||
/////// 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
如果同一個實體的兩個宣告附屬於不同的模組,則程式格式錯誤;如果彼此都不可達,則不需要診斷。
/////// decls.h int f(); // #1, attached to the global module int g(); // #2, attached to the global module
/////// Module interface of M module; #include "decls.h" export module M; export using ::f; // OK, does not declare an entity, exports #1 int g(); // Error: matches #2, but attached to M export int h(); // #3 export int k(); // #4
/////// Other translation unit import M; static int h(); // Error: matches #3 int k(); // Error: matches #4
以下宣告不附屬於任何命名模組(因此宣告的實體可以在模組之外定義)
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).
[編輯] 注意
特性測試宏 | 值 | 標準 | 特性 |
---|---|---|---|
__cpp_modules |
201907L |
(C++20) | 模組 — 核心語言支援 |
__cpp_lib_modules |
202207L |
(C++23) | 標準庫模組 std 和 std.compat |
[編輯] 關鍵詞
private, module, import, export
[編輯] 缺陷報告
下列更改行為的缺陷報告追溯地應用於以前出版的 C++ 標準。
缺陷報告 | 應用於 | 釋出時的行為 | 正確的行為 |
---|---|---|---|
CWG 2732 | C++20 | 關於可匯入標頭檔案是否可以 對匯入點的預處理器狀態做出反應,不明確 |
不作反應 |
P3034R1 | C++20 | 模組名和模組分割槽可以 包含定義為類物件宏的識別符號 |
已禁止 |