由於打算寫本電子書,所以重新審視了C++20 Moudle的部分 語法的不是這篇的重點,這篇講的是
如何跟CMake搭配使用
測試環境是Linux + Clang20 + CMake 3.28
Prerequisites 首先先clone git repo ,所有的變化都由範例legacy
開始,這是沒有Module之前的做法
CMake CMake已經是事實上的標準
Case1 Normal case 詳細內容請觀看 module_1
目錄,這邊只講我覺得重要的地方 這個Case就是legacy
直接翻譯成Module版本 首先看MathFunctions
的CMakeLists.txt的部分
1 2 3 4 5 6 7 8 9 10 11 12 13 14 target_sources(MathFunctions PUBLIC FILE_SET primary_interface TYPE CXX_MODULES FILES MathFunctions.cppm PRIVATE FILE_SET implementaion_units TYPE CXX_MODULES FILES src/mysqrt.cppm PRIVATE src/MathFunctions.cxx )
這邊有兩個FILE_SET
primary_interface:也就是我們要對外提供的Primary module interface unit
implementaion_units:內部的partion unit,不對外輸出 所以在安裝的時候,只會將MathFunctions.cppm
複製到安裝的目錄下Case2: Multiple Primary Module Interface Units 接著我們稍微修改MathFunctions.cppm
的內容1 2 3 4 5 6 7 8 9 module ;export module Math;export import :detail;export namespace mathfunctions{ double sqrt (double ) ; }
我們也將detail的內容也輸出了,因此我們需要做以下的修改
detail module和namespace需要標記成 export
1 2 3 4 5 6 7 8 9 10 module ;#include <math.h> export module Math:detail;export namespace mathfunctions::detail { double sqrt (double x) { return ::sqrt (x); } }
修改我們的CMakeLists.txt的部分1 2 3 4 5 6 7 8 9 10 target_sources(MathFunctions PUBLIC FILE_SET primary_interface TYPE CXX_MODULES FILES MathFunctions.cppm src/mysqrt.cppm PRIVATE src/MathFunctions.cxx )
現在我們有了兩個Primary Module Interface Units,在安裝的時候也要同時複製兩個檔案Math.detail
和Math:detail
的情況類似,所以就不說了
接著來研究Mitgrate的部分,這是參考clang Transitioning to modules 的部分
Case3: Mitgrate legacy to module (Part1) 看一下transform_1
的目錄 這邊主要的差別在於CMakeLists.txt
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 target_sources(MathFunctions PUBLIC FILE_SET export_headers TYPE HEADERS BASE_DIRS include/ FILES include/MathFunctions.h PUBLIC FILE_SET primary_interface TYPE CXX_MODULES FILES MathFunctions.cppm PRIVATE src/MathFunctions.cxx src/mysqrt.h src/mysqrt.cxx ) install(TARGETS MathFunctions EXPORT MathFunctionsTargets ARCHIVE FILE_SET export_headers FILE_SET primary_interface DESTINATION lib/cmake/MathFunctions/src )
既保留原有的leagcy code,更新增了一個Primary Module Interface Units 而MathFunctions.cppm
的內容則是
1 2 3 4 5 6 7 8 module ;#include "MathFunctions.h" export module Math;export namespace mathfunctions { using mathfunctions::sqrt ; }
將Global Module Fragment中的內容導出到Module中 這種方法不會破壞原有leagcy code,殺傷力最小
Case4: Mitgrate legacy to module (Part2) 看一下transform_21
的目錄,CMakeLists.txt跟上面一樣不變 改變的是MathFunctions.cppm
和MathFunctions.h
此時的MathFunctions.cppm
長這樣
1 2 3 4 5 6 7 8 9 module ;export module Math;#define IN_MODULE_INTERFACE extern "C++" { #include "MathFunctions.h" }
而MathFunctions.h
的內容則是
1 2 3 4 5 6 7 8 9 10 11 #pragma once #ifdef IN_MODULE_INTERFACE #define EXPORT export #else #define EXPORT #endif namespace mathfunctions { EXPORT double sqrt (double x) ; }
由於只有在Module狀態下,IN_MODULE_INTERFACE才會發揮作用,因此leagcy code的情況下會維持不變 這個方法雖然比上面麻煩,不過可以順利遷移到下一個階段
Case5: Mitgrate legacy to module (Part3) 所有方案中最麻煩的一種 主要思想是在implemtation unit當中切開legacy
和module
的實作,強迫Consumer只能使用其中一種,例如原先的Header可能要加上export
1 2 3 4 5 6 7 8 9 10 11 #pragma once #ifdef IN_MODULE_INTERFACE #define EXPORT export #else #define EXPORT #endif namespace mathfunctions { EXPORT double sqrt (double x) ; }
以及Implementation的部分也要隔開
1 2 3 4 5 6 7 8 9 10 11 12 #ifndef IN_MODULE_INTERFACE #include "MathFunctions.h" #include "mysqrt.h" #else module Math;#endif namespace mathfunctions { double sqrt (double x) { return detail::sqrt (x); } }
在這裡我選擇對CMakeLists.txt動手腳
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 if (ENABLE_MODULE_BUILD)target_sources(MathFunctions PUBLIC FILE_SET export_headers TYPE HEADERS BASE_DIRS include/ FILES include/MathFunctions.h include/mysqrt.h PUBLIC FILE_SET primary_interface TYPE CXX_MODULES FILES MathFunctions.cppm PRIVATE src/MathFunctions.cxx src/mysqrt.cxx ) else ()target_sources(MathFunctions PUBLIC FILE_SET export_headers TYPE HEADERS BASE_DIRS include/ FILES include/MathFunctions.h PRIVATE include/mysqrt.h src/MathFunctions.cxx src/mysqrt.cxx ) endif() target_compile_definitions(MathFunctions PRIVATE $<$<BOOL:${ENABLE_MODULE_BUILD} >:IN_MODULE_INTERFACE> ) if (ENABLE_MODULE_BUILD)install(TARGETS MathFunctions EXPORT MathFunctionsTargets ARCHIVE FILE_SET export_headers FILE_SET primary_interface DESTINATION lib/cmake/MathFunctions/src ) else ()install(TARGETS MathFunctions EXPORT MathFunctionsTargets ARCHIVE FILE_SET export_headers ) endif()
當我們指定ENABLE_MODULE_BUILD
的時候,會自動處理細節的部分 不過這邊也遇到了clang文件中的問題
Minor issue 由於我們之前的mysqrt.h
是經由src/MathFunctions.cxx
所include的,改成Module之後,這個相依性被切斷了 因此我們需要在MathFunctions.cppm
強迫加入
1 2 3 4 5 6 7 module ;export module Math;#include "MathFunctions.h" module : private ;#include "mysqrt.h"
這樣沒有問題,不過
原來的mysqrt.h
不需要公開,現在變成強迫要公開了
更好的方法是直接使用Module Partition Unit,也就是要改寫