跳到正文

Wiki

modules 簡介

約 6 分鐘閱讀

本文由簡體中文內容確定性轉換,並受版本化術語表保護。

1.本節解決什麼問題

傳統的 #include 有三大問題:

  1. 編譯慢:每個 .cpp 文件都要重新解析所有 include 的頭文件(可能幾萬行)。
  2. 宏污染#define 會影響所有 include 之後的代碼。
  3. 順序依賴:include 的順序可能影響程序行為。

Modules 是 C++20 引入的新特性,從根本上解決了這些問題——更快編譯、隔離性好、無宏泄漏。

2.這個特性是什麼

Modules 是 C++20 引入的模塊系統。和 #include(文本複製粘貼)不同,模塊是預編譯的接口聲明,只導出你想暴露的部分,內部實現完全隱藏。

3.C++ 標準版本

C++20(正式引入),C++23 增強了 import std; 標準庫模塊。

4.需要的頭文件

Modules 不需要頭文件。模塊文件通常用 .cppm 擴展名(社區慣例)或 .ixx(MSVC)。

5.基本語法

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

運行結果:程序正常結束,終端沒有額外輸出。

6.核心概念

概念 説明
export module 模块名; 聲明一個模塊(放在模塊接口文件中)
export 標記對外可見的函數/類/變量
import 模块名; 導入一個模塊
import <头文件>; 導入傳統頭文件(把它當作模塊來用,編譯器支持有限)
模塊接口單元 .cppm 文件,包含 export module
模塊實現單元 .cpp 文件,包含 module 模块名;(不帶 export)

7.include 和 module 的核心區別

不要把 modules 理解成"把 #include 換成 import"這麼簡單。二者的編譯模型不同,所以能解決的問題也不同。

對比點 #include import module
工作方式 文本複製,把頭文件內容粘進當前文件 導入已經編譯好的模塊接口
可見性 頭文件裏寫了什麼,包含者基本都能看到 只有 export 的名字對外可見
宏容易向後污染 模塊接口不會像文本包含那樣傳播宏
編譯依賴 include 順序可能影響結果 模塊依賴更明確
構建要求 所有編譯器和構建系統都成熟支持 需要較新的編譯器和構建系統

所以現階段學習 modules,重點是理解"接口和實現分離得更徹底"。真正項目中是否遷移,要看工具鏈是否穩定支持。

8.示例代碼

8.1.示例 1:最簡單的模塊

文件 1:math_module.cppm(模塊接口)

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

文件 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

編譯命令(以 GCC 為例)

# 先编译模块接口
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

運行結果

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

8.2.示例 2:模塊 + 類

文件: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";
    }
};

文件:main.cpp

import person;

int main()
{
    Person p("Alice", 25);
    p.print();

    // p.name_ = "Bob";  // ❌ 编译错误!name_ 是 private

    return 0;
}

運行結果

Alice, age 25

8.3.示例 3:在示例 2 基礎上,把接口和實現拆開

示例 1 和示例 2 為了直觀,把實現直接寫在模塊接口裏。真實項目裏更常見的寫法是:接口單元只導出聲明,實現單元放函數體。

文件 1:counter.cppm(模塊接口單元)

export module counter;

export class Counter
{
    int value_ = 0;

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

文件 2:counter.cpp(模塊實現單元)

module counter;

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

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

文件 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;
}

運行結果

counter = 8

9.運行結果

見上方每個示例的"運行結果"。

10.示例中的關鍵語法解釋

示例 講了什麼 新出現的語法 為什麼這樣寫 注意事項
示例 1 基礎模塊定義和導入 export moduleimportexport 模塊只暴露 export 的內容 模塊文件名沒有硬性規定,.cppm 是社區慣例
示例 2 模塊中導出類 export classimport <string> 類的 public/private 仍然有效 import <header> 屬於 header unit,工具鏈支持差異較大
示例 3 接口單元和實現單元分離 module counter; 接口只暴露聲明,實現放到單獨文件 構建系統要知道模塊依賴關係

11.常見錯誤

錯誤 1:編譯器不支持或未啓用

g++ -std=c++17 main.cpp  # ❌ C++17 没有 modules

正確做法:用 -std=c++20 -fmodules-ts(GCC/Clang),MSVC 用 /std:c++20

錯誤 2:忘記了 export

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

正確做法:export int func() { return 42; }

錯誤 3:編譯順序不對

必須先編譯模塊接口單元(.cppm),再編譯使用模塊的文件。

正確做法:用 CMake 3.28+ 或構建系統管理模塊依賴。

錯誤 4:把 private 當成模塊隱藏的全部

export module demo;

export class A
{
private:
    int value_;
};

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

private 控制的是類成員訪問權限;export 控制的是模塊級名字是否對外可見。二者解決的問題不同。

12.使用建議

  1. 現階段可以瞭解但不強制遷移:C++20 modules 的編譯器支持仍在完善中,傳統 #include 在相當長時間內仍是主流。
  2. 新項目可以嘗試:如果使用最新編譯器(GCC 15+、Clang 17+、MSVC 2022+),modules 已比較穩定。
  3. CMake 3.28+ 支持 modulestarget_sources(myapp PUBLIC FILE_SET CXX_MODULES FILES ...)
  4. 標準庫模塊 import std; 是 C++23 的特性:C++20 只能 import 自定義模塊。
  5. 理解 modules 的設計思想很重要:它代表了 C++ 的未來方向。
  6. 教程裏先學概念,不強背命令:不同編譯器的模塊命令差異很大,實際項目交給 CMake/構建系統管理更穩。

13.小結

  • Modules 是 C++20 的新編譯模型,替代 #include,更快、更安全。
  • export module 聲明模塊名,export 標記對外可見的聲明。
  • import 導入模塊(替代 #include)。
  • 模塊接口單元負責暴露 API,模塊實現單元負責隱藏實現。
  • 編譯器支持在不斷完善中,可以瞭解但現階段項目中使用需要評估。
  • 學習 modules 的設計有助於理解大型 C++ 項目的模塊化思想。