Ch 31: import 模块系统
- 理解 import 语法和模块语义
- 掌握 export module 声明
- 学会模块分区和模块命名空间
- 理解模块与头文件的区别和互操作
- 理解当前编译器支持状态
31.1 import vs #include
Section titled “31.1 import vs #include”Python 的 import
Section titled “Python 的 import”# Python - import 模块/包import os # 导入标准库from collections import deque # 导入特定符号import mypackage.mymodule # 导入子模块from mypackage import func # 导入特定函数
# as 别名import numpy as npfrom pandas import DataFrame as DFC++ 的 import
Section titled “C++ 的 import”// C++20 import - 导入命名空间或模块import std; // 导入 std 命名空间(未来 C++26)import std.core; // 导入 std 的 core 子模块(未来)import std.vector; // 导入 std::vector(未来)
// 当前(2024):只能 import 已声明的模块import mymodule; // 导入自定义模块import :helpers; // 导入分区(如果模块支持)
// 不能 import 标准库(大多数编译器不支持)// import <vector> // 错误语法#include <vector> // 当前只能用 #include| 方面 | Python import | C++ import |
|---|---|---|
| 目标 | 模块/包 | 命名空间或模块单元 |
| 解析时机 | 运行时(lazy) | 编译时 |
| 符号查找 | 自动 | 需 using 或限定 |
| 冲突处理 | as 别名 | 作用域限定 |
31.2 模块声明基础
Section titled “31.2 模块声明基础”export module 语法
Section titled “export module 语法”// 基本模块声明export module MyModule; // 导出整个模块module MyModule; // 仅内部模块(不导出)
// 模块名称规则:// - 必须是有效标识符或用 : 分隔的标识符序列// - 不能使用点号(.),用冒号(:)表示层次export module math; // OKexport module myapp.core; // OK (模块分区)export module myapp::core; // 等价export module std; // 保留给标准库简单模块示例
Section titled “简单模块示例”// ===== greetings.cpp - 模块实现文件 =====module; // 可选,表示这是模块接口
export module greetings;
// 模块内私有部分(不影响外部)module privately;
void private_helper() { // 仅模块内部使用}
// 导出函数export std::string greet(const std::string& name) { return "Hello, " + name + "!";}
export int add(int a, int b) { return a + b;}
// 导出类export class Calculator {public: int add(int a, int b) { return a + b; } int multiply(int a, int b) { return a * b; }};// ===== main.cpp - 使用模块 =====import greetings;
int main() { std::cout << greet("World") << "\n"; // Hello, World! std::cout << add(1, 2) << "\n"; // 3
Calculator calc; std::cout << calc.multiply(3, 4) << "\n"; // 12}模块实现文件 vs 接口文件
Section titled “模块实现文件 vs 接口文件”// ===== mymath.cppm - 接口文件(.cppm 是常用扩展名)=====module;export module mymath;
// 导出声明export int multiply(int a, int b);export double divide(double a, double b);
// 导出类型export class Matrix {public: Matrix(int rows, int cols); // ...};
// ===== mymath.cpp - 实现文件 =====module mymath; // 导入自己的模块(非导出)
// 实现int multiply(int a, int b) { return a * b;}
// 也可以在实现文件中导出(inline)export int another_func() { return 42; }31.3 模块分区
Section titled “31.3 模块分区”// 主模块export module foo;
// foo:detail 分区export module foo:detail; // foo 的 detail 子模块
// foo:impl 分区export module foo:impl;
export void hidden_helper() { // 仅 foo 内部使用}
// 内部函数(不导出)namespace foo { namespace detail { void internal_func() { } }}import foo; // 导入主模块import foo:detail; // 导入 detail 分区(如果需要)import foo:impl; // 导入 impl 分区(如果需要)
// 使用foo::some_exported_function();foo::detail::helper_function(); // 可能不可见(取决于导出)分区适用场景
Section titled “分区适用场景”// ===== 大型库的模块划分 =====
export module network;export void connect(const std::string& host);export void disconnect();
// network:types.cppmexport module network:types;export class Socket { /* ... */ };export struct Address { /* ... */ };
// network:impl.cppmmodule network:impl;// 实现细节,不导出class SocketImpl { /* 平台特定实现 */ };31.4 import 语义详解
Section titled “31.4 import 语义详解”import vs using
Section titled “import vs using”// import - 导入整个模块/命名空间import std; // 导入 std 命名空间(未来)
// using - 声明使用特定符号using std::cout; // 只导入 coutusing namespace std; // 导入整个 std(不推荐)
// import vs using 的区别import std;
void func() { cout << "Hello\n"; // 直接用 cout(通过 import std) // std::cout 仍然有效}模块和命名空间冲突
Section titled “模块和命名空间冲突”// a.cpp - 模块 Aexport module A;export void helper() { }
// b.cpp - 模块 Bexport module B;export void helper() { }
// main.cppimport A;import B;
int main() { // helper() 存在歧义 - 哪个模块的? // A::helper(); // 需要限定 // B::helper();
A::helper(); // 明确指定}import 和 header units
Section titled “import 和 header units”// C++20 允许将头文件转为 "header unit"import <vector>; // 将 vector.h 转为 header unit(实验性)import <iostream>; // 大多数编译器不支持
// 当前(2024)最佳实践:// - 标准库仍用 #include// - 第三方库用 #include// - 自定义模块用 import31.5 模块与命名空间
Section titled “31.5 模块与命名空间”模块创建命名空间
Section titled “模块创建命名空间”export module math;
export namespace math { int add(int a, int b) { return a + b; } double PI = 3.14159; class Calculator { /* ... */ };}
// 使用import math;
int main() { math::Calculator calc; auto pi = math::PI;}模块内的 namespace
Section titled “模块内的 namespace”export module logger;
export namespace logger { enum class Level { Debug, Info, Warning, Error };
class Logger { public: void log(Level level, const std::string& msg); };}
namespace logger { // 内部实现(不导出) void write_to_file(const std::string& msg); std::string format_message(Level level, const std::string& msg);}模块的全局命名空间
Section titled “模块的全局命名空间”module;export module global;
export int global_value = 42;
// 使用import global;
int main() { int x = global_value; // 直接访问}31.6 模块与类的关系
Section titled “31.6 模块与类的关系”模块内定义类
Section titled “模块内定义类”export module shape;
export class Point {public: Point(double x, double y) : x_(x), y_(y) {}
double distance_to(const Point& other) const { double dx = x_ - other.x_; double dy = y_ - other.y_; return std::sqrt(dx * dx + dy * dy); }
private: double x_, y_;};
export class Circle {public: Circle(Point center, double radius) : center_(center), radius_(radius) {}
double area() const { return 3.14159 * radius_ * radius_; }
private: Point center_; double radius_;};import shape;
int main() { Point p1(0, 0); Point p2(3, 4);
Circle c(p1, 5);
std::cout << p1.distance_to(p2) << "\n"; // 5 std::cout << c.area() << "\n"; // ~78.54}模块类和头文件类的区别
Section titled “模块类和头文件类的区别”| 特性 | 模块类 | 头文件类 |
|---|---|---|
| 编译速度 | 快(只编译一次) | 慢(每次都解析) |
| 名称查找 | 无宏污染 | 可能被宏干扰 |
| 虚函数表 | 正常 | 正常 |
| 模板实例化 | 需要显式 | 自动 |
| 导出控制 | export 关键字 | class 前加 export |
31.7 混用模块和头文件
Section titled “31.7 混用模块和头文件”头文件包含模块
Section titled “头文件包含模块”#ifndef MYMODULE_H#define MYMODULE_H
// 如果编译器支持,优先使用模块#ifdef __cpp_modulesimport mymodule; // 模块接口#else#include "mymodule.hpp" // 回退到头文件#endif
#endif
// mymodule.hpp// 传统头文件实现(当模块不可用时)class MyClass { /* ... */ };模块导入第三方库头文件
Section titled “模块导入第三方库头文件”// C++20 允许导入某些头文件作为 header units// 大多数编译器支持有限
// 当前最佳实践(2024)#include <spdlog/spdlog.h> // 第三方库用头文件#include <nlohmann/json.hpp> // header-only 库
import myapp.core; // 自定义模块用 import
int main() { spdlog::info("Hello"); // 正常工作}// wrapper.h - 桥接头文件#pragma once
// 包含所有需要的头文件#include <vector>#include <string>#include <memory>
// 定义所有导出class MyClass { /* ... */ };void helper_function();// mymodule.cppm - 模块接口module;export module mymodule;
// 重新导出需要的内容export import std;export import wrapper;31.8 当前编译器支持
Section titled “31.8 当前编译器支持”支持状态(2024)
Section titled “支持状态(2024)”| 编译器 | 模块支持 | 备注 |
|---|---|---|
| GCC | 11+ | 基本支持,实验性 |
| Clang | 15+ | 基本支持,实验性 |
| MSVC | 19.29+ | 基本支持,较完善 |
| ICC | 2021.10+ | 部分支持 |
| NVCC | 不支持 | GPU 代码不支持 |
// 1. std 模块不支持import std; // 失败import std.core; // 失败
// 2. import <header> 有限支持import <vector>; // 可能失败,取决于编译器
// 3. 模块分区可能不完全import mymodule:detail; // 某些编译器可能不支持
// 4. CMake 支持不完善// 需要设置 CMAKE_CXX_MODULE_FLAG 或使用实验性选项# GCC 14g++ -std=c++20 -fmodules-ts -xc++-module mymodule.cppmg++ -std=c++20 -fmodules-ts main.cpp mymodule.gcm
# Clang 17clang++ -std=c++20 -fmodules main.cpp
# MSVCcl /std:c++20 /exportHeader main.cpp31.9 项目结构建议
Section titled “31.9 项目结构建议”推荐目录结构
Section titled “推荐目录结构”/project├── src/│ ├── main.cpp│ ├── math/│ │ ├── math.cppm # 模块接口│ │ └── math.cpp # 模块实现│ ├── utils/│ │ ├── utils.cppm│ │ └── utils.cpp│ └── lib/│ ├── lib.cppm│ └── lib.cpp├── include/ # 对外 API(如果需要头文件兼容)│ ├── mylib.h│ └── mylib_export.h└── build/CMake 配置
Section titled “CMake 配置”cmake_minimum_required(VERSION 3.25)project(MyProject CXX)
set(CMAKE_CXX_STANDARD 20)set(CMAKE_CXX_STANDARD_REQUIRED ON)
# 模块支持(实验性)option(ENABLE_MODULES "Enable C++20 modules" OFF)
if(ENABLE_MODULES) target_compile_options(${PROJECT_NAME} PRIVATE $<$<CXX_COMPILER_ID:GNU>:-fmodules-ts> $<$<CXX_COMPILER_ID:Clang>:-fmodules> )endif()
# 源文件add_executable(myapp src/main.cpp src/math/math.cpp src/utils/utils.cpp)
target_include_directories(myapp PRIVATE include)从头文件迁移到模块
Section titled “从头文件迁移到模块”// ===== 阶段1:头文件 =====#pragma once#include <string>class Utils {public: static std::string format(const std::string& s);};
// ===== 阶段2:混合(模块 + 头文件)=====// utils.cppmmodule;export module utils;
export import std; // 未来#include <string> // 当前
// 保留原有接口export class Utils {public: static std::string format(const std::string& s);};
// ===== 阶段3:完全模块化 =====// utils.cppmmodule;export module utils;
export import std;
export class Utils {public: static std::string format(const std::string& s);};31.10 最佳实践
Section titled “31.10 最佳实践”// ✅ 使用模块组织大型库export module mylib;
// ✅ 明确导出需要公开的接口export class PublicClass { };class InternalClass { }; // 不导出,内部使用
// ✅ 模块分区用于大型项目export module mylib:core; // 核心接口export module mylib:extra; // 扩展功能
// ✅ 混用头文件和模块(当前)#include <spdlog/spdlog.h> // 第三方import mymodule; // 自定义
// ✅ 使用 .cppm 扩展名区分模块接口文件// mylib.cppm - 模块接口// mylib.cpp - 模块实现// ❌ 不要导出实现细节export class InternalHelper { }; // 不要
// ❌ 不要在模块内使用 #define 宏(会污染)#define MAX_SIZE 100 // 避免
// ❌ 不要混用模块分区和命名空间export module foo:bar; // 分区,不是命名空间namespace foo { // 这是命名空间 module bar; // 错误}
// ❌ 不要假设 std 模块可用import std; // 当前会失败模块命名建议
Section titled “模块命名建议”// 推荐:简短、有意义的模块名export module math;export module logger;export module network;
// 避免:过长或过于具体export module com::company::project::module::submodule;
// 分区命名export module network:http; // 网络的 HTTP 部分export module network:tcp; // 网络的 TCP 部分export module json:parser; // JSON 的解析部分31.11 章节总结
Section titled “31.11 章节总结”import 语法速查:
| 语法 | 说明 |
|---|---|
export module M; | 声明并导出模块 M |
module M; | 声明内部模块(非导出) |
import M; | 导入模块 M |
export import M; | 重新导出模块 M |
module M:N; | 模块 M 的 N 分区 |
import M:N; | 导入模块 M 的 N 分区 |
module; | 模块接口文件的开始 |
模块 vs 头文件:
| 方面 | 模块 | 头文件 |
|---|---|---|
| 编译速度 | 快 | 慢 |
| 宏隔离 | 完全 | 可能污染 |
| 重复编译 | 无 | 有 |
| 标准库支持 | 否(未来) | 是 |
| 第三方库 | 有限 | 完整 |
当前建议:
- 大型项目用模块加速编译
- 标准库仍用
#include - 第三方库用
#include - 混用是安全的
- 等待
import std;标准化(C++26)
继续学习:查看 ch28 了解更多 C++20 现代特性。