وحدات C++20
تعرف على سبب استبدال الوحدات النمطية للرؤوس، وكيفية الإعلان عن الوحدات النمطية وتصديرها، وأقسام الوحدة النمطية، ومسار الانتقال من التعليمات البرمجية المستندة إلى الرأس.
لماذا تحل الوحدات محل الرؤوس
لقد كان نظام#includeهو الأساس لتجميع C++ لعقود من الزمن، لكنه يحمل مشاكل خطيرة. تكون الرؤوس مضمنة نصيًا — يقوم المعالج المسبق بنسخ الملف بأكمله إلى كل وحدة ترجمة. هذا يعني أن#include قد يلصق أكثر من 50000 سطر من التعليمات البرمجية في كل ملف.cpp. النتيجة: تجميع بطيء، هش يتضمن الترتيب (وحدات الماكرو من رأس واحد تؤثر على رأس آخر)، و انتهاكات قاعدة التعريف الواحدة (ODR) التي تسبب سلوكًا صامتًا غير محدد.
تعمل وحدات C++20 على حل كل هذه المشكلات. يتم تجميع الوحدة مرة واحدة في واجهة ثنائية، ثم يتم استيرادها بواسطة المستهلكين دون إعادة التحليل. لا يوجد أي تضمين نصي، ولا تسرب للماكرو، ولا تبعية لأمر التضمين. يمكن أن تتحسن أوقات البناء بمقدار 5-20x للمشاريع الكبيرة.
إعلان الوحدة وتصديرها
تحتوي الوحدة على وحدة واجهة (توضح ما تم تصديره) وواحدة أو أكثر من وحدات التنفيذ بشكل اختياري. الكلمة الأساسيةexportتجعل الإعلانات مرئية للمستوردين:
// 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;
}
}استيراد الوحدات
يستخدم المستهلكونimportللوصول إلى الإعلانات المصدرة للوحدة النمطية. على عكس#include، لا يقومimportبتنفيذ التضمين النصي — لا تتسرب وحدات الماكرو وتفاصيل التنفيذ عبر حدود الوحدة النمطية:
// 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 mainأقسام الوحدة النمطية
يمكن تقسيم الوحدات الكبيرة إلى أقسام — وحدات فرعية تشكل معًا واجهة الوحدة الكاملة. الأقسام داخلية للوحدة ولا يمكن استيرادها بواسطة المستهلكين الخارجيين مباشرة:
// --- 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 hypotenuseمعيار الاستيراد (C++23)
يقدم C++23import std;الذي يستورد المكتبة القياسية بأكملها كوحدة نمطية. يعد هذا بمثابة تغيير في سرعة التجميع:
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).
لا يزال دعم المترجم في مرحلة النضج: اعتبارًا من عام 2024، تدعم جميع الوحدات النمطية دول مجلس التعاون الخليجي وClang وMSVC ولكن بمستويات مختلفة من الاكتمال وتكامل نظام بناء مختلف. يتمتع MSVC بالدعم الأكثر نضجًا.
إنشاء تكامل النظام: يدعم الإصدار CMake 3.28+ الوحدات النمطية التي تحتوي على ميزةCXX_MODULE_SETS، لكن النظام البيئي (الماسحات الضوئية التابعة، ومديري الحزم) لا يزال يلحق بالركب. أصبح ترتيب البناء أمرًا مهمًا الآن - يجب تجميع واجهات الوحدات النمطية قبل عملائها.
لا يوجد تصدير للماكرو : لا تقوم الوحدات عن عمد بتصدير وحدات ماكرو#define. يجب أن تتعامل التعليمات البرمجية التي تعتمد على وحدات الماكرو من الرؤوس (مثلWIN32_LEAN_AND_MEANأوNOMINMAX) مع هذا الأمر بشكل مختلف.
استراتيجية الترحيل: ابدأ بـimport std;(C++23) لاستبدال محتويات المكتبة القياسية، ثم قم بتقسيم التعليمات البرمجية الخاصة بك تدريجيًا بدءًا من المكتبات الورقية التي تحتوي على القليل من التبعيات.
عند اعتماد الوحدات:
1. حافظ على مرونة واجهات الوحدات — قم بتصدير ما يحتاجه المستهلكون فقط. تتمتع الكيانات غير المصدرة بوحدة ربط ولا يمكنها التسبب في حدوث انتهاكات لـ ODR.
2. استخدم الأقسام للوحدات الكبيرة — قم بالتقسيم إلى وحدات فرعية منطقية (على سبيل المثال،:types، و:algorithms، و:io).
3. تفضلexport namespace — قم بتصدير مساحات الأسماء بأكملها بدلاً من الإعلانات الفردية للحفاظ على الواجهة منظمة.
4. لا تخلط بين#includeوimportلنفس المكتبة — اختر واحدة وكن متسقًا داخل وحدة الترجمة.
5. استخدم ملحقات.cppmأو.ixx لملفات واجهة الوحدة حتى تتمكن أنظمة البناء من التعرف عليها تلقائيًا.
- تستبدل الوحدات
#includeبـimport- لا يوجد تضمين نصي، ولا تسرب للماكرو، ولا توجد أخطاء في ترتيب التضمين - استخدم
exportلجعل الإعلانات مرئية للمستوردين؛ الكيانات غير المصدرة خاصة بالوحدة النمطية - تقوم أقسام الوحدة النمطية بتقسيم الوحدات الكبيرة إلى مكونات فرعية أثناء تقديم واجهة موحدة
- يستبدل
import std;(C++23) جميع محتويات المكتبة القياسية باستيراد واحد سريع - يجب أن تقوم أنظمة البناء بتجميع واجهات الوحدات النمطية قبل المستهلكين، فترتيب التجميع مهم
اختبار — اختبر معرفتك
(15 نقطة خبرة)1. ما هي الميزة الأساسية للوحدات النمطية على #include؟
2. ماذا يحدث لوظيفة محددة في الوحدة ولكن لم يتم تمييزها بـ`export`؟
3. ماذا يفعل`import std;`في C++23؟