C++20 Modules 尝鲜

Modules是C++ 20标准里面一个非常重要的新特性,一个模块的所有接口文件、实现文件,作为一个翻译单元,一次编译后生成 pcm,之后遇到 Import 该模块的代码,编译器会从 pcm 中寻找函数声明等信息,该特性会极大加快 C++ 代码的编译速度。

以《C++高级编程》第五版中第一章中25_ConstAirlineTicket源码为例,体验下将Header Files改变为Module Interface Files的过程,注意module、export、import等关键字。

A C++ module interface unit with an interface only

AirlineTicket.cppm:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
module;
import <string>;

export module airline_ticket;
export class AirlineTicket
{
public:
double calculatePriceInDollars() const;

std::string getPassengerName() const;
void setPassengerName(std::string name);

int getNumberOfMiles() const;
void setNumberOfMiles(int miles);

bool getHasEliteSuperRewardsStatus() const;
void setHasEliteSuperRewardsStatus(bool status);

private:
std::string m_passengerName{ "Unknown Passenger" };
int m_numberOfMiles{ 0 };
bool m_hasEliteSuperRewardsStatus{ false };
};

A C++ module implementation unit

AirlineTicket.cpp:

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
module airline_ticket;

import <string>;
using namespace std;

double AirlineTicket::calculatePriceInDollars() const
{
if (getHasEliteSuperRewardsStatus()) {
// Elite Super Rewards customers fly for free!
return 0;
}

// The cost of the ticket is the number of miles times 0.1.
// Real airlines probably have a more complicated formula!
return getNumberOfMiles() * 0.1;
}

std::string AirlineTicket::getPassengerName() const
{
return m_passengerName;
}

void AirlineTicket::setPassengerName(string name)
{
m_passengerName = name;
}

int AirlineTicket::getNumberOfMiles() const
{
return m_numberOfMiles;
}

void AirlineTicket::setNumberOfMiles(int miles)
{
m_numberOfMiles = miles;
}

bool AirlineTicket::getHasEliteSuperRewardsStatus() const
{
return m_hasEliteSuperRewardsStatus;
}

void AirlineTicket::setHasEliteSuperRewardsStatus(bool status)
{
m_hasEliteSuperRewardsStatus = status;
}

Programs that import modules

AirlineTicketTest.cpp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import <iostream>;
import airline_ticket;
#include <fmt/format.h>

using namespace std;

int main()
{
AirlineTicket myTicket; // Stack-based AirlineTicket
myTicket.setPassengerName("Sherman T. Socketwrench");
myTicket.setNumberOfMiles(700);
double cost{ myTicket.calculatePriceInDollars() };
std::cout << fmt::format("This ticket will cost ${}", cost) << std::endl;
}

C++编译器方面,目前MSVC对C++20支持最齐全,其次是GCC、Clang,而GCC和Clang在Module编译命令有些不同,以Clang为例,编写对应的CMake编译文件如下:

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
cmake_minimum_required(VERSION 3.16)
set(CMAKE_C_COMPILER /usr/bin/clang)
set(CMAKE_CXX_COMPILER /usr/bin/clang++)
set(CMAKE_CXX_FLAGS " -Wall -Werror -O3 -DNDEBUG ")
set(CMAKE_EXE_LINKER_FLAGS " -lc++ -lc++abi ")

project(25_ConstAirlineTicket)

find_package(fmt)

set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
set(PREBUILT_MODULE_PATH ${CMAKE_BINARY_DIR}/modules)

function(add_module name)
file(MAKE_DIRECTORY ${PREBUILT_MODULE_PATH})
add_custom_target(${name}.pcm
COMMAND
${CMAKE_CXX_COMPILER}
-std=c++20
-stdlib=libc++
-fmodules
-x c++
-c
${CMAKE_CURRENT_SOURCE_DIR}/${ARGN}
-Xclang -emit-module-interface
-o ${PREBUILT_MODULE_PATH}/${name}.pcm
)
endfunction()

add_module(AirlineTicket AirlineTicket.cppm)

add_compile_options(-stdlib=libc++)
add_compile_options(-fmodules)
add_compile_options(-fbuiltin-module-map)
add_compile_options(-fimplicit-module-maps)
add_compile_options(-fmodule-file=${PREBUILT_MODULE_PATH}/AirlineTicket.pcm)

# Program name and sources
set (TARGET main)
set (SOURCES AirlineTicket.cpp AirlineTicketTest.cpp)

add_executable(${TARGET} ${SOURCES})
add_dependencies(${TARGET} AirlineTicket.pcm)
target_link_libraries(${TARGET} PRIVATE fmt::fmt-header-only)

# cmake .. && make

在源码文件夹中,新建build文件夹,进入build后终端上执行cmake .. && make 。