Skip to content

Wiki

Introduction to modules

About 11 min read

Existing English translations were reused. 3 current source blocks remain in Simplified Chinese, without triggering paid translation.

1.What problem does this section solve?

Traditional #include has three major problems:

  1. Slow compilation:Every .cpp file must reparse all header files included via include (possibly tens of thousands of lines).
  2. Macro pollution:#define affects all code after the include.
  3. Order dependency:The order of includes may affect program behavior.

Modules are a new feature introduced in C++20 that fundamentally solve these problems—faster compilation, better isolation, and no macro leakage.

2.What is this feature?

Modules is the module system introduced in C++20. Unlike #include (text copy-paste), modules are precompiled interface declarations that export only the parts you want to expose, while the internal implementation is completely hidden.

3.C++ Standard Edition

C++20 (officially introduced), C++23 enhanced the import std; standard library module.

4.Required header files

Modules do not require header files. Module files typically use the .cppm extension (community convention) or .ixx (MSVC).

5.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;
}

Run result: The program terminates normally, and the terminal has no additional output.

6.Core Concepts

概念 说明
export module 模块名; 声明一个模块(放在模块接口文件中)
export 标记对外可见的函数/类/变量
import 模块名; 导入一个模块
import <头文件>; 导入传统头文件(把它当作模块来用,编译器支持有限)
模块接口单元 .cppm 文件,包含 export module
模块实现单元 .cpp 文件,包含 module 模块名;(不带 export)

7.The core difference between include and module

Do not understand modules as simply "replacing #include with import". The two have different compilation models, so the problems they can solve are also different.

Comparison point #include import module
Working method Copy text, paste the header file contents into the current file Import an already-compiled module interface
Visibility Whatever is written in the header file is basically visible to includers Only the name of export is visible externally
Macros Macros tend to leak outward Module interfaces do not propagate macros the way text inclusion does
Compilation dependency Include order may affect the result Module dependencies are clearer
Build requirements All compilers and build systems support it maturely Requires relatively new compilers and build systems

So at this stage, when learning about modules, the focus is on understanding that "the separation of interface and implementation is more thorough." Whether to migrate in a real project depends on whether the toolchain supports it stably.

8.Example code

8.1.Example 1: The simplest module

Document 1: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 command (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

Running result:

add(3, 5) = 8
subtract(10, 3) = 7

8.2.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;
}

Running result:

Alice, age 25

8.3.Example 3: Based on Example 2, separate the interface from the implementation.

Examples 1 and 2 put the implementation directly in the module interface for clarity. A more common approach in real projects is: the interface unit exports only declarations, and the implementation unit contains the function bodies.

Document 1:counter.cppm(Module Interface Unit)

export module counter;

export class Counter
{
    int value_ = 0;

public:
    void add(int n);
    int value() const;
};

Document 2:counter.cpp(Module Implementation Unit)

module counter;

void Counter::add(int n)
{
    value_ += n;
}

int Counter::value() const
{
    return value_;
}

Document 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;
}

Running result:

counter = 8

9.Operation result

See the "run results" for each example above.

10.Key syntax explanation in the example

Example What was discussed Newly introduced syntax Why it is written this way Notes
Example 1 Basic module definition and import export module、import、export A module only exposes what is exported There is no hard rule for module file names,.cppm is a community convention
Example 2 Exporting classes from a module export class、import <string> Class public/private is still valid import <header> belongs to a header unit, and toolchain support varies significantly
Example 3 Separating interface units and implementation units module counter; The interface exposes only declarations, while the implementation is placed in a separate file The build system needs to know module dependencies

11.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), MSVC uses /std:c++20.

Error 2: Forgot export

// module.cppm
export module my_module;
int func() { return 42; }  // ❌ 没有 export,外部看不到!

Correct approach:export int func() { return 42; }

Error 3: Incorrect compilation order

The module interface unit (.cppm), must be compiled first before compiling files that use the module.

Correct approach: use CMake 3.28+ or a build system to manage module dependencies.

Mistake 4: Treating private as the entirety of module hiding

export module demo;

export class A
{
private:
    int value_;
};

int helper(); // 没有 export,外部看不到

private controls the access permissions of class members;export controls whether module-level names are visible externally. The two solve different problems.

12.Usage Recommendations

  1. 现阶段可以了解但不强制迁移:C++20 modules 的编译器支持仍在完善中,传统 #include 在相当长时间内仍是主流。
  2. 新项目可以尝试:如果使用最新编译器(GCC 15+、Clang 17+、MSVC 2022+),modules 已比较稳定。
  3. CMake 3.28+ 支持 modules:target_sources(myapp PUBLIC FILE_SET CXX_MODULES FILES ...)。
  4. 标准库模块 import std; 是 C++23 的特性:C++20 只能 import 自定义模块。
  5. 理解 modules 的设计思想很重要:它代表了 C++ 的未来方向。
  6. 教程里先学概念,不强背命令:不同编译器的模块命令差异很大,实际项目交给 CMake/构建系统管理更稳。

13.Summary

  • Modules are a new compilation model for C++20, replacing #include, being faster and safer.
  • export module declares the module name, and export marks declarations visible externally.
  • import imports modules (replacing #include).
  • A module interface unit is responsible for exposing the API, while a module implementation unit is responsible for hiding the implementation.
  • Compiler support is continuously improving; it can be understood, but its use in current projects requires evaluation.
  • Learning the design of modules helps in understanding the modularization concepts of large C++ projects.