最近看了兩篇關於 C++ 20 Modules 很有意思的文章,戳:
《Understanding C++ Modules: Part 1: Hello Modules, and Module Units》
《Understanding C++ Modules: Part 2: export, import, visible, and reachable》
眾所周知,C++靠前處理器處理標頭檔案的方法被詬病已久。在 C++17 Module TS 後,標準委員會的 dalao 們終於在 C++20 把 Modules 併入標準= =。(此處應該有掌聲?
那麼我們要怎麼建立一個 Module 呢?標準引入了新的關鍵字 import、module,並使用保留關鍵字 export 來匯入、定義和匯出 Module。
// hello_world.cpp
export module demos.hello.world;
export auto get_text()
{
return "Hello C++ Modules!";
}
// main.cpp
import demos.hello.world;
import <iostream>;
int main()
{
std::cout << get_text() << std::endl;
}
這是一個 C++20 Modules 版的 Hello World。注意 export module xxx.yyy.zzz 是一個 Module 的匯出定義,函式 get_text 加上 export 就成為 Module 的匯出符號。
Module 的名字可以是 aaa.bbb.ccc.ddd 這樣的,此處借鑑了其他語言的命名規範,提升了可讀性。
根據標準,每個 Module 都是一個 TU(Translation Unit) ,這樣就可以在構建時得到更好的 Cache 編譯的效果,縮短編譯時間。
最後說下 import <iostream> 這樣的用法。這是為了相容以前老的標頭檔案的寫法,意思是把一個標頭檔案當作一個獨立的 TU 來編譯,並且避免了名稱空間汙染和主檔案中符號的影響。
-------------華麗分割線1--------------
一些語言,如 C# 有 partial class 的語法,可以把一個完整的 class 拆分為多個檔案,最後合併編譯。C++ Modules 提供了一個特性叫 Module Partition,可以把 Module 拆分在多個 .cpp 檔案中定義。
在標準中,使用 Module 名稱 + 冒號 + Partition 名稱 的方式來定義一個 Module Partition。
// home.cpp
export module home;
export import :father;
export import :mother;
// home_father.cpp
export module home:father;
export auto get_father_name()
{
return "Xiaoming";
}
// home_mother.cpp
export module home:mother;
export auto get_mother_name()
{
return "Xiaohong";
}
// main.cpp
import home;
int main()
{
auto father = get_father_name();
auto mother = get_mother_name();
}
這樣 father 和 mother 就成為 Module home 的兩個 Partition。吐槽一下 export import :xxx 這種語法,它的意思是把 Partition 先 import 進來再 re-export 出去,讓使用者可見。
這裡多了一個概念:Module Interface Unit。很 Simple,對於一個 .cpp 檔案,如果是 export module xxx 這樣的,就是一個 Module Interface Unit,意思是匯出介面單元。
比如上面的 home.cpp, home_father.cpp 和 home_mother.cpp 都是 Module Interface Unit。
-------------華麗分割線2--------------
除了 Module Interface Unit,還有一個對應的東西叫 Module Implementation Unit ,模組實現單元。
同樣很 Easy,如果一個 .cpp 檔案中定義的是 module xxx,注意前面沒有 export ,那麼它就是一個 Module Implementation Unit。
// animal.cpp
export module animal;
import :dogs;
import :cats;
export auto get_cat_name();
export auto get_dog_name();
// animal_cats.cpp
module animal:cats;
// "export" is not allowed here.
auto get_cat_name()
{
return "狗·德川家康·薛定鄂·保留";
}
// animal_dogs.cpp
module animal:dogs;
// "export" is not allowed here.
auto get_cat_name()
{
return "DOGE";
}
// main.cpp
import animal;
int main()
{
auto cat_name = get_cat_name();
auto dog_name = get_dog_name();
}
Partition cats 和 dogs 就是兩個 Module Implementation Unit ,而 animal.cpp 是一個 Module Interface Unit。
注意 Implementation Unit 裡面不允許有 export 出現,且需要在 Interface Unit 中 import 對應的 Partition。
想偷懶?同志好想法,還有種不需要 Partition 的簡化寫法。
// animal.cpp
export module animal;
export auto get_cat_name();
export auto get_dog_name();
// animal_impl.cpp
module animal;
auto get_cat_name()
{
return "狗·德川家康·薛定鄂·保留";
}
auto get_cat_name()
{
return "DOGE";
}
是不是感覺似曾相識?這個和目前的老式標頭檔案宣告和定義分離的用法如出一轍。
animal_impl.cpp 定義的還是叫 Module Implementation Unit ,animal.cpp 叫 Module Interface Unit 。
WHY? 既然可以寫在一起,為什麼要分離呢。一個是滿足程式碼習慣,還有一個就是如果頻繁修改實現,可以不動介面,提高增量編譯速度~
-------------華麗分割線3--------------
考慮到程式設計師編碼的方便性,標準規定了五花八門的 export 語法。如下所示,我們來欣賞一下。
// Export everything within the block.
export
{
int some_number = 123;
class foo
{
public:
void invoke() { }
private:
int count_ = 0;
};
}
// Export namespace.
export namespace demo::test
{
struct tips
{
int abc;
}
void free_func() { }
}
// Export a free function.
export void here_is_a_function() { }
// Export a global variable.
export int global_var = 123;
// Export a class.
export class test
{
};
下面幾種是非法的 export 寫法,是無法編譯的。
// Anonymous namespace cannot be exported.
export namespace
{
}
// Cannot export static variables.
export static int static_variable = 123;
// Cannot export static functions.
export static void foo()
{
}
// OK, export a namespace.
export namespace test
{
// Error, cannot define static members in an exported namespace.
static int mine = 123;
// Error, as mentioned above.
static void geek() { }
}
-------------華麗分割線4--------------
文中還介紹了其他的一些坑,比如 Implementation Unit Beast 、Reachability & Visibility 巴拉巴拉,大家可以參考上面兩篇文章……
個人覺得 Modules 作為重量級特性,還是給 C++ 帶來了革新。引用一句某老外的說法:“Make CPP great again!”。
期待編譯器這邊的實現和新的 Build System 及可能的包管理工具,貌似一直在推進。
------- Over -------