Modules Introduction
What problem does this section solve?
Traditional #include has three major problems:
- Slow compilation: Every
.cppfile requires re-parsing all included header files (potentially tens of thousands of lines). - Macro pollution:
#defineaffects all code following its inclusion. - Sequential dependencies: The order of includes may affect program behavior.
Modules are a new feature introduced in C++20 that fundamentally solves these problems—faster compilation, better isolation, and no macro leakage.
What is this feature?
Modules are a module system introduced in C++20. Unlike #include (text copy and paste), modules are precompiled interface declarations that only export the parts you want to expose, completely hiding internal implementations.
C++ standard version
C++20 (formally introduced), C++23 enhanced the import std; standard library module.
Required header files
Modules do not need header files. Module files usually use the .cppm extension (community convention) or .ixx (MSVC).
Basic Syntax
// math_module.cppm —— 模块接口文件
export module math; // 声明模块名
export int add(int a, int b) // export:对外可见
{
return a + b;
}
int multiply(int a, int b) // 没有 export:模块内部可见
{
return a * b;
}
// main.cpp —— 使用模块
import math; // 导入模块(替代 #include)
int main()
{
int x = add(3, 5); // ✅ 可以访问
// int y = multiply(3, 5); // ❌ 不可访问(没有 export)
return 0;
}
运行结果:程序正常结束,终端没有额外输出。
Core Concepts
| concept | Explanation |
|---|---|
export module 模块名; | Declare a module (in the module interface file) |
export | Mark externally visible functions/classes/variables |
import 模块名; | Import a module |
import <头文件>; | Importing Traditional Header Files (using it as a module, compiler support is limited) |
| Module Interface Unit | .cppm file, contains export module microcontroller tutorial. |
| Module implementation unit | The file, containing the module 模块名; (without export) |
Include 与模块的核心区别
在编程中,include(或 import)和 模块(module) 都是用于组织和管理代码的机制,但它们在实现、作用和效率上有显著差异。以下是它们的核心区别对比:
| 特性 | Include / 导入 | 模块(Module) |
|---|---|---|
| 本质 | 文本替换或文件包含(编译预处理阶段) | 编译单元,独立的二进制或可复用组件 |
| 编译过程 | 每次编译都会重新解析包含的文件,可能重复编译 | 编译一次生成独立模块,后续直接复用,避免重复编译 |
| 依赖管理 | 依赖链可能隐晦,易导致循环依赖或命名冲突 | 显式声明依赖关系,结构清晰,便于管理 |
| 封装性 | 通常无封装,所有内容直接暴露在当前作用域 | 支持封装,可控制公开接口和内部实现 |
| 构建效率 | 大型项目编译慢,因为重复处理相同代码 | 显著提升构建速度,模块可独立编译和缓存 |
| 典型场景 | 传统C/C++的 #include、Python 的 import 语句 | 现代语言特性(如ES模块、Rust模块、Java 9模块) |
简要说明:
include/import- 类似于“复制粘贴”代码(文本层面),常见于C/C++的预处理或Python的导入语句。
- 可能引发重复编译、头文件依赖地狱(C/C++)等问题。
- 例如:
#include <stdio.h>或import os。
- 模块(Module)
- 作为独立的编译单元或包,提供清晰的边界和接口定义。
- 支持按需加载、依赖隔离和版本管理,更适合大型工程。
- 例如:ES模块(
export/import)、Rust模块系统。
选择建议:
- 传统项目/简单脚本:
include/import通常足够且灵活。 - 大型项目/长期维护:优先使用模块系统,以提升可维护性和构建效率。
模块化是现代软件工程的趋势,但具体选择需结合语言特性和项目需求。
Don't understand modules as simply "replacing #include with import." The compilation models for the two are different, so the problems they can solve also differ.
| Compare points | #include | import module |
|---|---|---|
| working mode | Copy the text, paste the header file content into the current file. | Import the precompiled module interface. |
| visibility | What's written in the header files can generally be seen by those who include them. | Only the name of export is externally visible. |
| macro | Macros can easily cause backward contamination. | Module interfaces won't propagate macros like text inclusion does. |
| compilation dependency | The include order may affect the result. | Module dependencies are more explicit. |
| Build Requirements | All compilers and build systems are maturely supported. | Needs a newer compiler and build system. |
So the current focus of learning modules is to thoroughly understand "the separation of interface and implementation." Whether to migrate in real projects depends on whether the toolchain provides stable support.
Example code
Example 1: The simplest module
File math_module.cppm (Module Interface)
export module math;
export int add(int a, int b)
{
return a + b;
}
export int subtract(int a, int b)
{
return a - b;
}
// 内部函数,不对外暴露
int internal_helper()
{
return 0;
}
File 2: main.cpp
import math;
#include <iostream>
int main()
{
std::cout << "add(3, 5) = " << add(3, 5) << "\n";
std::cout << "subtract(10, 3) = " << subtract(10, 3) << "\n";
// internal_helper(); // ❌ 编译错误!没有 export
return 0;
}
运行结果:见下方“运行结果”;模块示例需要先按编译命令生成模块接口,再运行 main.cpp。
Compilation Commands (using GCC as an example):
# 先编译模块接口
g++ -std=c++20 -fmodules-ts -c math_module.cppm -o math_module.o
# 再编译主程序
g++ -std=c++20 -fmodules-ts -c main.cpp -o main.o
# 链接
g++ math_module.o main.o -o program
Results:
add(3, 5) = 8
subtract(10, 3) = 7
Example 2:Module + Class
File: person_module.cppm
export module person;
import <string>;
import <iostream>;
export class Person
{
std::string name_;
int age_;
public:
Person(const std::string& name, int age)
: name_(name), age_(age) {}
void print() const
{
std::cout << name_ << ", age " << age_ << "\n";
}
};
File: main.cpp
import person;
int main()
{
Person p("Alice", 25);
p.print();
// p.name_ = "Bob"; // ❌ 编译错误!name_ 是 private
return 0;
}
Results:
Alice, age 25
Example 3: On the basis of Example 2, separate the interface and implementation
For clarity, Examples 1 and 2 write the implementation directly within the module interface. In real-world projects, a more common practice is to have the interface unit only export declarations, while the implementation unit contains the function bodies.
File 1: counter.cppm (Module Interface Unit)
export module counter;
export class Counter
{
int value_ = 0;
public:
void add(int n);
int value() const;
};
File 2: counter.cpp (Module Implementation Unit)
module counter;
void Counter::add(int n)
{
value_ += n;
}
int Counter::value() const
{
return value_;
}
File 3: main.cpp
import counter;
#include <iostream>
int main()
{
// 程序从 main 函数开始执行,下面的语句会按顺序运行。
Counter c;
c.add(3);
c.add(5);
std::cout << "counter = " << c.value() << "\n";
// 返回 0 表示程序正常结束。
return 0;
}
Results:
counter = 8
runtime results
See the "running results" for each example above.
Key syntax explanation in the example
|Here is the translation of the provided Simplified Chinese Markdown fragment into natural American English, following all specified rules.
| Example | Discusses what | Newly emerged syntax | Why write it this way | Precautions |
|---|---|---|---|---|
| Example 1 | Basic Module Definitions and Imports | export module、import、export | The module only exposes the exported content. | There is no strict rule for module file naming; .cppm is a community convention. |
| Example 2 | Export a class from a module. | export class、import <string> | The public/private access modifiers in classes remain effective. | import <header> belongs to header units, and toolchain support for them varies significantly. |
| Example 3 | Separation of interface unit and implementation unit | module counter; | The interface only exposes declarations, with implementations placed in separate files. | When building a system, module dependencies need to be known. |
Common Errors
Error 1: Compiler Not Supported or Not Enabled
g++ -std=c++17 main.cpp # ❌ C++17 没有 modules
Correct approach: Use -std=c++20 -fmodules-ts (GCC/Clang), use /std:c++20 for MSVC.
Error 2: Forgot export
// module.cppm
export module my_module;
int func() { return 42; } // ❌ 没有 export,外部看不到!
Correct approach: export int func() { return 42; }
Error 3: Compilation order is incorrect
You must first compile the module interface unit (.cppm) before compiling the files that use the module.
Proper approach: Use CMake 3.28+ or a build system to manage module dependencies.
Error 4: Mistaking 'private' for the entirety of module hiding
export module demo;
export class A
{
private:
int value_;
};
int helper(); // 没有 export,外部看不到
private governs class member access permissions; export determines whether module-level names are visible externally. They address different problems.
使用建议
- 明确目标:在开始前确定您的具体需求,以便选择最合适的工具或教程。
- 充分利用资源:参考官方文档、教程和博客,这些资料能帮助您快速上手并解决问题。
- 实践应用:通过动手操作项目或编写代码来巩固学习成果,提升实际操作能力。
- 问题解决:遇到困难时,查阅参考资料或寻求社区支持,逐步培养独立解决问题的能力。
- 分享经验:完成项目后,可以撰写文章或博客分享心得,帮助其他学习者。
如果需要针对特定领域(如单片机、机器人或环境搭建)的进一步建议,请提供更多信息,我将为您细化内容。
- Currently optional but not required for migration: Compiler support for C++20 modules is still evolving, and traditional
#includewill remain mainstream for a considerable period. - New projects can try: If using the latest compilers (GCC 15+, Clang 17+, MSVC 2022+), modules are now relatively stable.
- CMake 3.28+ supports modules:
target_sources(myapp PUBLIC FILE_SET CXX_MODULES FILES ...). - The standard library module
import std;is a C++23 feature: C++20 only allows importing custom modules. - Understanding the design philosophy of modules is crucial: they represent the future direction of C++.
- Learn concepts in tutorials, don't force-memorize commands: Compiler-specific module commands vary greatly; for actual projects, it's more stable to let CMake/build systems handle them.
Summary
- Modules are the new compilation model in C++20, replacing
#includeto be faster and safer. - Declare the module name with
export module, and useexportto mark declarations as publicly visible. importimport module (alternative to#include).- The module interface unit is responsible for exposing the API, while the module implementation unit is responsible for hiding the implementation.
- Compiler support is continuously being improved. You can explore it, but its use in current projects requires evaluation.
- Studying the design of modules helps in understanding modular thinking in large C++ projects.