0%

Introduction to C++20 Module

The simplest module

先看範例,就是Module版的Hello World

1
2
3
export module hello;
export void hello_world() {};
void non_export_func() {}

而Consumer Module的一方就這樣寫

1
2
3
4
5
6
7
import hello;
int main()
{
hello_world(); // OK
non_export_func(); // Cannot compile
return 0;
}

Description

從這個範例當中,Consumer這邊不用特別說
這邊要說的是如何寫個Module

Module Unit

在C++20,有了一個新的Compile Unit,就是Module Unit,所有Module Unit的Top Level Statement都是有module關鍵字的
module前面有沒有export就是決定這是哪一種Module Unit

  • export的叫作Module Interface Unit
  • export的叫做Module Implementation Unit

Module Implementation Unit後面再說

The content of a module

一個Module擁有

  • 一個以上的Module Interface Unit
  • 零個以上的Module Implementation Unit

且每個Module裡面有且唯一一個Primary Module Interface Unit

在Hello World這個範例當然只有Primary Module Interface Unit 的存在,至於什麼是Primary Module Interface Unit,也是後面再說

export

在上面的範例,我們定義了兩個函數

1
2
export void hello_world() {};
void non_export_func() {}

不塗於傳統的header file方式,如果是傳統的header file,兩個function應該都可以被外界可見,而Module Unit只有export出的符號才能輩Connsumer看到
export的其他用法還有這樣

1
2
3
4
5
6
7
8
// export entire namespace
export namespace hello {}

// export the symbols in the block
export {
int e = 1;
void test() {}
}

Module Implementation Unit

就像傳統header/implementation的方法,我們可以把declaration/implementation分離,因此我們有了Module Implementation Unit
重寫我們的範例,將implementation分開
因此我們的Module Interface Unit就變成

1
2
3
export module hello;
export void hello_world();
void non_export_func();

而Module Implementation Unit則是

1
2
3
module hello;
void hello_world() {};
void non_export_func() {}

如同之前所說的,module前面沒加export的就是Module Implementation Unit,而在function implementation前面也沒加export,就跟傳統的方式很像

My thought on Module Implementation Unit

之前declaration/implementation被人詬病的一點,就是你要維護兩份狀態,當你declaration改了之後,如果implementation沒改,會產生不可預料的後果,運氣好的話是編譯不過,運氣不好產生深層的Bug更難解

如同之前所說的,一個Module可以不必擁有Module Implementation Unit
那存在的必要是什麼?

我認為是將舊有的Source Code Mitigation到C++ Module的方式
如同現在流行的header only library一樣,未來的Module應該僅由Module Interface Unit組成

Import other module

寫Module時不免使用到其他Module,讓我們定義一個新的Module

1
2
export module world;
export struct obj {};

而我們的hello module就變成這樣

1
2
3
export module hello;
import world;
export void hello_world(obj o) {};

注意,import只能放在top level module declaration之下,不能交換順序

接著要回去看Consumer的部分了

Visibility control

此時我們的Consumer會是這樣

1
2
3
4
5
6
7
8
import hello;
import world;
int main()
{
obj o;
hello_world(o);
return 0;
}

這裡該注意的點,在hello module當中雖然import了world,但是不
會再次輸出symbol到hello module metadata中
因此如果Consumer沒加上import world時,會發現找不到obj的情形

但如果我們將hello改成這樣

1
2
3
export module hello;
export import world;
export void hello_world(obj o) {};

這邊將我們import進來的Module再度export出去,這也是我們細分module的基礎
那麼Consumer不加import world也是可以正常運行

Divide module into small parts

當一個Module大起來之後,要降低複雜度,細分成更小的Block是需要的,而其中又有兩種方法

Sobmodule

我們將hello_world分成兩個function
一個放在hello.sub_a,另外一個放在hello.sub_b
直接看程式碼

1
2
export module hello.sub_a;
export void hello() {};

而另外一個就不貼了,看看我們hello module的定義

1
2
3
export module hello;
export import hello.sub_a;
export import hello.sub_b;

Reexport出hello.sub_ahello.sub_b的exported symbol

Note

hello.sub_ahello_sub_b是各自獨立完整的Module,submodule機制只是邏輯組合,讓他們看起來像是同一個Module
所以你Consumer這樣寫也是可以的

1
2
3
4
5
6
7
8
import hello.sub_a;
import hello.sub_b;
int main()
{
hello();
world();
return 0;
}

Module partition

不同於submodule,partition所分的sub partition不能個別存在
一樣直接看程式碼

1
2
export module hello:part_a;
export void hello() {};

跟上面很像,不過將.改成了:
而我們的hello module則是

1
2
3
export module hello;
export import :part_a;
export import :part_b;

這邊有幾點要注意的

  • 一個module name當中沒有:出現的就是Primary Module Interface Unit,如同之前所說
    一個以上的Module Interface Unit,有且唯一一個Primary Module Interface Unit
    這個範例有三個Module Interface Unit,只有hello是Primary Module Interface Unit
    hello.sub_a則是一個獨立的Module,只是邏輯上看起來是同一個Mdoule

  • Partition只能接受import :part_a的語法,import hello:part_a是不對的

  • Consumer只能寫import hello

Global Module Fragment

Global Module Fragment是提供preprocessor使用的空間,因此你可以在這邊定義Marco,或是include未被moduleized的header file,而在這邊定義的symbol則不會輸出到module interface中,因此不會汙染全局環境

Global Module Fragment必須在export module之前,就像這樣

1
2
3
4
5
module;
#define MAX(a, b) (((a) > (b)) ? (a) : (b))
#include <string>
#include <vector>
export module hello;

Reference