C++ 模块已成为常态。
C++ Modules Are Here to Stay

原始链接: https://faresbakhit.github.io/e/cpp-modules/

## C++20 模块:总结 C++20 引入了模块,这是一次重要的演进,旨在解决语言构建系统和复杂性方面长期存在的问题。与传统的基于头文件的方案(需要 `#include` 指令)不同,模块提供了封装性和更快的编译速度。 关键概念包括**模块单元**(取代翻译单元/`.cpp` 文件)、**导出声明**(明确定义对用户可见的内容)和**模块分区**——允许仅在更大的模块内部可见的内部模块。子模块在逻辑上分组,但编译器将它们视为单个单元的一部分,从而强制执行清晰的公共接口。 导入被简化:`import std;` 取代了 `#include `。 重要的是,模块通过“全局模块片段”保持**向后兼容性**,从而能够逐步采用现有的头文件库。 主要好处是**编译时间缩短**。作者使用模块与传统编译方法相比,编译速度提高了 8.6 倍,显著改善了开发流程。 虽然编译器和工具支持仍在不断完善(CMake 提供了良好的支持),但对于个人项目,甚至越来越多的商业项目而言,C++20 模块都是一项值得投资的技术。

相关文章

原文

Within C++, there is a much smaller and cleaner language struggling to get out.

—Bjarne Stroustrup

C++ enthusiasts will often bash you for using the C preprocessor instead of the latest great metaprogramming feature of the language but until recently, any meaningful use of C++ meant that you had to use at least one preprocessor directive (#include) and that is no longer true.

C++20 modules provide a way to encapsulate a library (or a namespace) such as Qt, cv, or std1.

Know your modules

Using modules is as easy as

import std;

auto main() -> int {
  std::println("Hello world!");
}

and creating your own module is no harder, but we ought to have some terminology laid down first:

  • Translation unit: Think of this as any .cpp file.
  • Module unit: This is one or more translation units that declares a module. You can declare everything in one file (that we call the interface unit) or separate your interface and implementation (like a .h file with a corresponding set of .cpp files).
  • Export declarations: Inside of your module unit, you can export declarations (classes, functions, etc.) that are importable by the users of your modules. Exports are explicit.

With those definitions out of the way, we can begin by declaring our first module, a data structures and algorithms module:


export module dsa;

namespace dsa {
export int pow(int a, int b) {
  ...
}
}

Well, that was easy. How about we add Red-Black Tree as a submodule?


export module dsa.rbtree;

export namespace dsa {
enum class AllowDuplicates : bool {
    No,
    Yes,
};

template<typename T, AllowDuplicates AllowDuplicates, typename Compare = std::less<T>>
class RedBlackTree {
  ...
}
}

It’s the same thing. In fact, from the compiler’s perspective submodules are not a thing; dsa.rbtree is to dsa what “openai” is to “open”.

Since there’s no such thing as “submodules”, there’s no way for modules to interact except by their public interfaces and that is by design. But this also means that you’ll have one gigantic module unit with many correlating parts and implementation details; navigating such code will be a nightmare.

Module partitions to the rescue, they’re module units that are only importable by their named module and the other module partitions under the named module.

For example, you’ve added a bunch of linked list variants to your DSA library and they all share the private Node structure, so you split your code (that is for the same module) into multiple dependent modules that are only visible to each other and to your module.


export module dsa.linked_list;

export import :circular_list;
export import :ordered_list;
export import :unordered_list;


import :node; 
              

export namespace dsa {
template <typename T>
class CircularSinglyLinkedList {
}
}

There’s one missing piece of the puzzle: backwards compatibility. Yes, you can use libraries that don’t support modules inside of modules and even upgrade your code incrementally to use modules through what is called the “global module fragment” (module;). There’s also a private module fragment, but we won’t discuss it.

module;
#define GLFW_INCLUDE_NONE
#include <GLFW/glfw3.h>
#include <glad/gl.h>
export module dsa.sortvis;

That’s about everything you need to know to get started using modules!

You might wonder, “Why go through the hassle when all of my C++ projects use header files and work perfectly fine?” Yes, that’s true but at some point the C model begins to show its age, particularly in the time it takes to compile a big project. That’s not to mention that with enough preprocessor hacks (or time until you resort to them,) your abstractions become leaky and you’re faced with Hyrum’s Law.

Fast compile times

It’s actually pretty easy to notice that C++ has a “compile time” problem. For quite some time, I’ve taken it upon myself to go through most of the CSES Problem Set and while in these competitive programming style problems, you spend most of your time analyzing the problem and coming up with a plausible algorithm, I’ve found the compile times of the major C++ compilers to be a real bottleneck; having to wait >4 seconds2 simply interrupts my flow.

It’s apparent from the µbenchmark on my solved problems that C++20 Modules are a clear winner and they provide an 8.6x speedup over the stock Clang and a 1.2x speedup over PCH. See footnotes3 for the competitive programming template and script using modules.

To not bore you to death and frankly I’d do a bad job at it, I’ll not explain how to work with C++20 modules in Clang, instead the amazing developers behind Clang wrote a comprehensive article: Standard C++ Modules — Clang documentation (You don’t need to read it unless you’re building tooling around Clang, which the CMake folks have already done.)

But support

Lagging support for modules from C++ vendors to tooling is a valid point to not consider modules at all in your projects. But your personal projects don’t need the guarantees C++ often holds for commercial projects (and I think most commericial projects don’t either), and the story is only half bad. As of now, most major compilers either implement the spec completely or partially and CMake provides complete modules support but experimental support for import std;, which is enough in my book.

Here’s the minimal CMakeLists.txt to get you started:

cmake_minimum_required(VERSION 3.28)

project(dsa)

set(CMAKE_CXX_SCAN_FOR_MODULES ON)
set(CMAKE_CXX_STANDARD 23)

add_library(dsa)
target_sources(dsa
  PUBLIC FILE_SET dsa_public_modules TYPE CXX_MODULES
  FILES
    src/dsa.cpp
)

add_executable(hello src/bin/hello.cpp)
target_link_libraries(hello PRIVATE dsa)

or if you really want to do import std; (EXPERIMENTAL), you need to add the following lines:


set(CMAKE_EXPERIMENTAL_CXX_IMPORT_STD "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX")
set(CMAKE_CXX_MODULE_STD ON)
联系我们 contact @ memedata.com