Mô-đun C++20
Tìm hiểu lý do mô-đun thay thế tiêu đề, cách khai báo và xuất mô-đun, phân vùng mô-đun và đường dẫn chuyển đổi từ mã dựa trên tiêu đề.
Tại sao mô-đun thay thế tiêu đề
The#includesystem has been the foundation of C++ compilation for decades, but it carries serious problems. Headers are textually included — the preprocessor copies the entire file into each translation unit. This means#include might paste 50,000+ lines of code into every.cppfile. The result: slow compilation, fragile include ordering (macros from one header affecting another), and One Definition Rule (ODR) violations that cause silent undefined behavior.
C++20 modules solve all of these problems. A module is compiled once into a binary interface, then imported by consumers without re-parsing. There is no textual inclusion, no macro leakage, and no include-order dependency. Build times can improve by 5-20x for large projects.
Module Declaration and Export
Một mô-đun có một đơn vị giao diện (khai báo nội dung được xuất) và một hoặc nhiều đơn vị triển khai tùy chọn. Từ khóaexporthiển thị các khai báo cho nhà nhập khẩu:
// math_utils.cppm — module interface unit
export module math_utils; // module declaration
import <cmath>; // import a header unit
// Exported — visible to importers
export double circle_area(double radius) {
return std::numbers::pi * radius * radius;
}
export struct Point {
double x, 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);
}
};
// NOT exported — module-private (internal linkage)
double helper_function(double val) {
return val * val;
}
// Export a namespace — all members become visible
export namespace geometry {
double triangle_area(double base, double height) {
return 0.5 * base * height;
}
double rectangle_area(double width, double height) {
return width * height;
}
}Nhập mô-đun
Người tiêu dùng sử dụngimportđể truy cập vào các khai báo đã xuất của mô-đun. Không giống như#include,importkhông thực hiện đưa văn bản vào - macro và chi tiết triển khai không bị rò rỉ qua ranh giới mô-đun:
// main.cpp — imports the module
import math_utils;
#include <iostream> // can still use #include for non-modularized headers
int main() {
double area = circle_area(5.0);
std::cout << "Circle area: " << area << "\n";
Point p1{0.0, 0.0};
Point p2{3.0, 4.0};
std::cout << "Distance: " << p1.distance_to(p2) << "\n"; // 5.0
std::cout << "Triangle: " << geometry::triangle_area(10, 5) << "\n";
// helper_function(42); // ERROR: not exported, not visible
return 0;
}
// Compile order matters for modules:
// 1. Compile the module interface first:
// g++ -std=c++20 -fmodules-ts -c math_utils.cppm
// 2. Then compile the consumer:
// g++ -std=c++20 -fmodules-ts main.cpp -o mainPhân vùng mô-đun
Các mô-đun lớn có thể được chia thành phân vùng — các mô-đun phụ cùng nhau tạo thành giao diện mô-đun hoàn chỉnh. Các phân vùng nằm bên trong mô-đun và không thể được người tiêu dùng bên ngoài nhập trực tiếp:
// --- math_utils-algebra.cppm (partition interface) ---
export module math_utils:algebra;
export double square(double x) { return x * x; }
export double cube(double x) { return x * x * x; }
// --- math_utils-trig.cppm (partition interface) ---
export module math_utils:trig;
import <cmath>;
export double deg_to_rad(double degrees) {
return degrees * std::numbers::pi / 180.0;
}
export double sin_deg(double degrees) {
return std::sin(deg_to_rad(degrees));
}
// --- math_utils.cppm (primary module interface) ---
export module math_utils;
// Re-export partitions — consumers see everything
export import :algebra;
export import :trig;
// Additional exports can go here
export double hypotenuse(double a, double b) {
return std::sqrt(square(a) + square(b));
}
// --- Consumer ---
// import math_utils; // gets algebra, trig, and hypotenusenhập tiêu chuẩn (C++23)
C++23 giới thiệuimport std;nhập toàn bộ thư viện tiêu chuẩn dưới dạng mô-đun. Đây là một yếu tố thay đổi cuộc chơi về tốc độ biên dịch:
import std; // Everything: iostream, vector, string, algorithm, ...
int main() {
std::println("Hello from C++23 modules!");
std::vector<int> v{1, 2, 3};
std::ranges::sort(v);
}import std; replaces dozens of #include directives with a single import that compiles dramatically faster because the standard library module is pre-compiled. There is also import std.compat; which additionally provides C library names in the global namespace (like printf, strlen).
Hỗ trợ trình biên dịch vẫn đang hoàn thiện: Kể từ năm 2024, GCC, Clang và MSVC đều hỗ trợ các mô-đun nhưng có mức độ hoàn thiện khác nhau và khả năng tích hợp hệ thống xây dựng khác nhau. MSVC có sự hỗ trợ trưởng thành nhất.
Xây dựng tích hợp hệ thống: CMake 3.28+ hỗ trợ các mô-đun có tính năngCXX_MODULE_SETS, nhưng hệ sinh thái (trình quét phụ thuộc, trình quản lý gói) vẫn đang bắt kịp. Thứ tự xây dựng bây giờ rất quan trọng - giao diện mô-đun phải được biên dịch trước người tiêu dùng.
Không xuất macro: Các mô-đun không xuất macro#definemột cách có chủ ý. Mã dựa trên macro từ tiêu đề (nhưWIN32_LEAN_AND_MEANhoặcNOMINMAX) phải xử lý vấn đề này theo cách khác.
Chiến lược di chuyển: Bắt đầu vớiimport std;(C++23) để thay thế các thư viện tiêu chuẩn, sau đó dần dần mô-đun hóa mã của riêng bạn, bắt đầu bằng các thư viện lá có ít phần phụ thuộc.
Khi áp dụng các mô-đun:
1. Giữ giao diện mô-đun gọn gàng — chỉ xuất những gì người tiêu dùng cần. Các thực thể không xuất khẩu có liên kết mô-đun và không thể gây ra vi phạm ODR.
2. Sử dụng phân vùng cho các mô-đun lớn — chia thành các mô-đun phụ hợp lý (ví dụ::types,:algorithms,:io).
3. Ưu tiênexport namespace — xuất toàn bộ không gian tên thay vì các khai báo riêng lẻ để giữ cho giao diện được ngăn nắp.
4. Không kết hợp#includevàimportcho cùng một thư viện — hãy chọn một và nhất quán trong một đơn vị dịch.
5. Sử dụng tiện ích mở rộng.cppmhoặc.ixx cho các tệp giao diện mô-đun để hệ thống xây dựng có thể tự động nhận dạng chúng.
- Các mô-đun thay thế
#includebằngimport- không bao gồm văn bản, không rò rỉ macro, không có lỗi thứ tự bao gồm - Sử dụng
exportđể hiển thị các tờ khai cho nhà nhập khẩu; các thực thể không xuất khẩu là mô-đun riêng tư - Phân vùng mô-đun chia các mô-đun lớn thành các thành phần phụ trong khi trình bày một giao diện thống nhất
import std;(C++23) thay thế tất cả các thư viện tiêu chuẩn bao gồm một lần nhập nhanh, duy nhất- Hệ thống xây dựng phải biên dịch giao diện mô-đun trước người tiêu dùng - vấn đề thứ tự biên dịch
Câu đố — Kiểm tra kiến thức
(15 XP)1. Ưu điểm chính của mô-đun so với #include là gì?
2. Điều gì xảy ra với một hàm được xác định trong mô-đun nhưng KHÔNG được đánh dấu bằng`export`?
3. `import std;`trong C++23 làm gì?