C++20 Modules - 让编译加速吧 | C++ · 传统与革新的空间

本篇文章同步发布于 blog

blog 好读版


前言

Modules 的好处

以往,假如你在一个 cpp file 中 #include 了某个 header file,Preprocessor 会把你要的 header file 引入,变成一个 translation unit
但如果你有多个档案都 #include 同一个 header,那 Preprocessor 就会每一个都引入一遍,造成编译速度缓慢。

在引入 C++20 Modules 之后,编译好的 modules 可以直接在各个地方被 compiler 利用,编译速度就可以大幅提升。
当然,Modules 带来好处不只编译速度的提升,还有封装、引入顺序不影响 macro 等优点。

Compiler 支持状况

https://en.cppreference.com/w/cpp/compiler_support
C++20 features > Modules

至目前为止,只有 MSVC 的支持最完整,GCC , Clang 都只有 partial,因此我们这里就以 MSVC 举例。

配置 Visual Studio

在开始之前

在开始之前,我们需要设定一下 Visual Studio,

https://en.cppreference.com/w/cpp/compiler_support
C++23 library features > Standard Library Modules

由于 Standard Library Modules 在 C++23 才引入,
为了处理旧有的 header,我们可以使用 header units

( 其实 msvc 有预先实作,但 IntelliSense 等方面还有些问题,有兴趣的人可以自行尝试 )

Prerequisites

Visual Studio 2019 16.10 or later,这里我直接用 Visual Studio 2022

Project Properties

C++ Language Standard 至少要设定 /std:c++20
Configuration Properties > General > C++ Language Standard

Scan Sources for Module Dependencies 设定成 Yes
Configuration Properties > C/C++ > General

副档名部分各家要求不太一样,Visual Studio 要求的是 Example.ixx
你可以直接在 solution explorer 中加入

add module

概述

这里先举个宣告跟实现合在一起的例子

// hello.ixxexport module helloworld; // module declarationimport <iostream>;        // import declarationexport void hello()       // export declaration{    std::cout << "Hello world!\n";}
// main.cppimport helloworld; // import declarationint main(){    hello();}
// ConsoleHello world!

hello.ixx

export module helloworld;
宣告并汇出一个 module , 名字为 helloworld
加上 export 代表这是一个 primary module interface unit

import <iostream>;
header unit 的方式 import 一个 header

export void hello();
要汇出一个 function , 在宣告前加上 export 即可

main.cpp

import helloworld;
import 一个 module , 名字为 helloworld, module 的名字为前面 export module 的名称,与档名无关

Module declarations - 宣告 Module

这里我们稍微修改一下前一个例子

// hello.ixxexport module helloworld; // module declarationimport <iostream>;        // import declarationexport void hello();     // export declaration
// hello.cppmodule helloworld; // declares a module implementation unit for named module 'helloworld'// export void hello() // ERROR: a declaration can be exported only from a module interface unitvoid hello()       // implementation for 'void hello()'{    std::cout << "Hello world!\n";}
// main.cppimport helloworld; // import declarationint main(){    hello();}

Module 可以分为以下几种 module units,

module interface unitmodule implementation unitprimary module interface unitmodule partition interface unitmodule partition implementation unit

module interface unit

module interface unit ,指的是 helloworld.ixx
在 module declaration 前有加上 export,表示其为 interface,相似于之前的 .h

负责宣告、导出 module name , namespaces , functions 等你想导出的东西,
module 的名字为 export module module-namemodule-name 所定义,与档名无关

module implementation unit

module implementation unit ,指的是 hello.cpp
在 module declaration 前没有 export,表示其为 implementation,相似于之前的 .cpp

负责实现 interface file 中的宣告,如同前面所说,名字为 module-name 所定义,与档名无关。

注意,在 implementation 中也可以进行 import,但 export 只能在 interface 中使用。

module partition units

module partition interface unit , module partition implementation unit ,
可以将单个 module 分成多个 partition,后面会再谈到,基本性质跟前面大同小异

primary module interface unit

primary module interface unit ,就这个例子来说,helloworld.ixx 符合其定义,
module 可能会因为 partition 的关係有多个 interface ,
但 primary module interface 每一个 module 都只能也必须有一个,
只要他不是宣告为 module partition ( export module A:B; ),那就会被视为 primary module interface,

dot 句点

举个例子来说,mymodule.mysubmodule
看到 dot,大家直觉应该都觉得是类似 class,
但在 module 中,dot 并没有特殊的含义,就只是名称的一部分,
然而,通常还是会把他拿来表示阶层的关係

Exporting declarations - 汇出宣告

export 除了可以用来汇出 module (export module A;) 外,也可以汇出宣告跟 namespace,
如果不想每个都打 export ,也可以把东西放在 { } 里,再全部 export

// hello.ixxexport module helloworld; // declares the primary module interface unit for named module 'helloworld'// zero() will be visible by translations units importing 'helloworld'export int zero() { return 0; }// one() will NOT be visible.int one()  { return 1; }// Both two() and three() will be visible.export{    int two()  { return 2; }    int three() { return 3; }}// Exporting namespaces also works: number::four() and number::five() will be visible.export namespace number{    int four()  { return 4; }    int five() { return 5; }}

Importing modules and headers - 汇入 modules 跟 headers

modules and headers

import 大致可以分为两种,一种是一般的 import module,另一种是前面提到的 import <headeer>
摆放的位置,要在 module declaration 后,在其他 declarations 前。

在 module 中,不应该直接使用 #include,如果要用,要把它放在 global module fragment

export-import

透过 module,可以避免我们在 module units 中 import 的东西,被使用 module 的人连带 import
但如果你想,可以透过 export 将其再次汇出。

// A.ixx (primary module interface unit of 'A')export module A; import <iostream>; // import headeerexport import <string_view>; // export-imports export void print(std::string_view message){    std::cout << message << std::endl;}
// main.cppimport A; // import moduleint main(){    std::string_view message = "Hello, world!";    print(message);}

Global module fragment

如果因为 macros 的关係需要用到 #define 来设定 headers ,我们可以把它放在 global module fragment 里。

位置在 module 的最开始,第一个宣告必须放 module;,範围到 module declaration 结束。

// A.ixx (primary module interface unit of 'A')module; // start // https://learn.microsoft.com/en-us/cpp/c-runtime-library/math-constants?view=msvc-170// Defining _USE_MATH_DEFINES to use Math Constants#define _USE_MATH_DEFINES // for C++#include <cmath> export module A; // endimport <iostream>;export void pi(){    std::cout << M_PI << std::endl; // 3.1415926}

Private module fragment

如果你想把宣告跟实现在单个档案中完成,你也可以选择把实现放在 Private module fragment

位置在 module 的最尾端,範围由 module : private; 开始。

export module A; export int one(); module : private; // The start of the private module fragment.int one()           {    return 1;}

Module partitions - 模组分区

最后这部分稍微有点複杂,
如同字面的意思,单个 Module 可以拆为多个 partitions (分区?),
语法为冒号后加名子,export module module-name:part-name

命名习惯上,通常是 <primary-module-name>-<module-partition-name> , ( e.g, A-B , A-C )一个 module partition 只能属于一个 module,(export module A:B; 属于 A )module partition 可以被其他 partition import , 语法为 import :part-name

在 primary module interface unit,除了 implementation,必须 export 所有 interface partitions ,
export import :part-name

export 所有 interface partitions 的规定是 No diagnostic is required,
所以 compiler 不一定会提醒,请注意

// A.ixxexport module A;     // primary module interface unitexport import :B;    // Hello() is visible when importing 'A'.export import :C;    // WorldImpl() is visible only for 'A.ixx'.// World() is visible by any translation unit importing 'A'.export void World(){    std::cout << WorldImpl() << '\n';}export int zero(){    return 0;}
// A-B.ixx export module A:B; // partition module interface unit// import :C;import <iostream>;// Hello() is visible by any translation unit importing 'A'.export void Hello() { std::cout << "Hello" << '\n';// std::cout << WorldImpl() << '\n'; // ERROR: WorldImpl() is not visible.}
// A-C.ixxexport module A:C; // partition module interface 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(){        Hello();    World();    // std::cout << zero() << '\n'; // ERROR: 'cout': undeclared identifier    // WorldImpl(); // ERROR: WorldImpl() is not visible.}

比较特别的是,module partition 一样可以用 implementation unit 的方式
但是要注意,implementation unit 是不能被 export 的,

另外到目前为止,在 MSVC 下,档案的 Compile as 需设定成 Module Internal Partition (/internalPartition )
C/C++ > Advanced > Compile as

//  A.cpp   export module A;     // primary module interface unit import :C;           // WorldImpl() is now visible only for 'A.cpp'.// export import :C; // ERROR: Cannot export a module implementation unit.
// 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"; }

结语

Modules 的部分终于暂时写完了,一开始看好像稍嫌複杂,其实不然,只是相同概念的组合
对于 C++20 的主要 feature ,算是大致介绍了一个,
用一篇就写完好像还是太多了? 本来想多切成几篇的说~

References

Overview of modules in C++ | Microsoft LearnModules (since C++20) - cppreference.comC++20: Structure Modules - ModernesCpp.comvisual c++ - C++ 20 experimental std Modules not accessible with latest MSVC experimental tools enabled - Stack OverflowWalkthrough: Build and import header units in Visual C++ projects | Microsoft Learn[C++20 Modules] Module Partition Implementation Unit Bug(?) - Visual Studio FeedbackUnderstanding C++ Modules: Part 1: Hello Modules, and Module Units (vector-of-bool.github.io)[module.unit] (eel.is)No Diagnostic Required - cppreference.com

关于作者: 网站小编

码农网专注IT技术教程资源分享平台,学习资源下载网站,58码农网包含计算机技术、网站程序源码下载、编程技术论坛、互联网资源下载等产品服务,提供原创、优质、完整内容的专业码农交流分享平台。

热门文章