Wiki
modules 簡介
本文由簡體中文內容確定性轉換,並受版本化術語表保護。
1.本節解決什麼問題
傳統的 #include 有三大問題:
- 編譯慢:每個
.cpp檔案都要重新解析所有 include 的標頭檔案(可能幾萬行)。 - 宏汙染:
#define會影響所有 include 之後的程式碼。 - 順序依賴: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 module、import、export |
模組只暴露 export 的內容 | 模組檔名沒有硬性規定,.cppm 是社群慣例 |
| 示例 2 | 模組中匯出類 | export class、import <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.使用建議
- 現階段可以瞭解但不強制遷移:C++20 modules 的編譯器支援仍在完善中,傳統
#include在相當長時間內仍是主流。 - 新專案可以嘗試:如果使用最新編譯器(GCC 15+、Clang 17+、MSVC 2022+),modules 已比較穩定。
- CMake 3.28+ 支援 modules:
target_sources(myapp PUBLIC FILE_SET CXX_MODULES FILES ...)。 - 標準庫模組
import std;是 C++23 的特性:C++20 只能 import 自定義模組。 - 理解 modules 的設計思想很重要:它代表了 C++ 的未來方向。
- 教程裡先學概念,不強背命令:不同編譯器的模組命令差異很大,實際專案交給 CMake/構建系統管理更穩。
13.小結
- Modules 是 C++20 的新編譯模型,替代
#include,更快、更安全。 export module宣告模組名,export標記對外可見的宣告。import匯入模組(替代#include)。- 模組介面單元負責暴露 API,模組實現單元負責隱藏實現。
- 編譯器支援在不斷完善中,可以瞭解但現階段專案中使用需要評估。
- 學習 modules 的設計有助於理解大型 C++ 專案的模組化思想。