第 18.21 節

Modules Introduction

0瀏覽次數0訪問次數--跳出率--平均停留

What problem does this section solve?

Traditional #include has three major problems:

  1. Slow compilation: Every .cpp file requires re-parsing all included header files (potentially tens of thousands of lines).
  2. Macro pollution: #define affects all code following its inclusion.
  3. 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

conceptExplanation
export module 模块名;Declare a module (in the module interface file)
exportMark 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 unitThe file, containing the module 模块名; (without export)

Include 与模块的核心区别

在编程中,include(或 import)和 模块(module) 都是用于组织和管理代码的机制,但它们在实现、作用和效率上有显著差异。以下是它们的核心区别对比:

特性Include / 导入模块(Module)
本质文本替换或文件包含(编译预处理阶段)编译单元,独立的二进制或可复用组件
编译过程每次编译都会重新解析包含的文件,可能重复编译编译一次生成独立模块,后续直接复用,避免重复编译
依赖管理依赖链可能隐晦,易导致循环依赖或命名冲突显式声明依赖关系,结构清晰,便于管理
封装性通常无封装,所有内容直接暴露在当前作用域支持封装,可控制公开接口和内部实现
构建效率大型项目编译慢,因为重复处理相同代码显著提升构建速度,模块可独立编译和缓存
典型场景传统C/C++的 #include、Python 的 import 语句现代语言特性(如ES模块、Rust模块、Java 9模块)

简要说明:

  1. include/import
    • 类似于“复制粘贴”代码(文本层面),常见于C/C++的预处理或Python的导入语句。
    • 可能引发重复编译、头文件依赖地狱(C/C++)等问题。
    • 例如:#include <stdio.h>import os
  2. 模块(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#includeimport module
working modeCopy the text, paste the header file content into the current file.Import the precompiled module interface.
visibilityWhat's written in the header files can generally be seen by those who include them.Only the name of export is externally visible.
macroMacros can easily cause backward contamination.Module interfaces won't propagate macros like text inclusion does.
compilation dependencyThe include order may affect the result.Module dependencies are more explicit.
Build RequirementsAll 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.


ExampleDiscusses whatNewly emerged syntaxWhy write it this wayPrecautions
Example 1Basic Module Definitions and Importsexport moduleimportexportThe module only exposes the exported content.There is no strict rule for module file naming; .cppm is a community convention.
Example 2Export a class from a module.export classimport <string>The public/private access modifiers in classes remain effective.import <header> belongs to header units, and toolchain support for them varies significantly.
Example 3Separation of interface unit and implementation unitmodule 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.

使用建议

  • 明确目标:在开始前确定您的具体需求,以便选择最合适的工具或教程。
  • 充分利用资源:参考官方文档、教程和博客,这些资料能帮助您快速上手并解决问题。
  • 实践应用:通过动手操作项目或编写代码来巩固学习成果,提升实际操作能力。
  • 问题解决:遇到困难时,查阅参考资料或寻求社区支持,逐步培养独立解决问题的能力。
  • 分享经验:完成项目后,可以撰写文章或博客分享心得,帮助其他学习者。

如果需要针对特定领域(如单片机、机器人或环境搭建)的进一步建议,请提供更多信息,我将为您细化内容。

  1. Currently optional but not required for migration: Compiler support for C++20 modules is still evolving, and traditional #include will remain mainstream for a considerable period.
  2. New projects can try: If using the latest compilers (GCC 15+, Clang 17+, MSVC 2022+), modules are now relatively stable.
  3. CMake 3.28+ supports modules: target_sources(myapp PUBLIC FILE_SET CXX_MODULES FILES ...).
  4. The standard library module import std; is a C++23 feature: C++20 only allows importing custom modules.
  5. Understanding the design philosophy of modules is crucial: they represent the future direction of C++.
  6. 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 #include to be faster and safer.
  • Declare the module name with export module, and use export to mark declarations as publicly visible.
  • import import 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.
音乐页