Ενότητες C++20
Μάθετε γιατί οι λειτουργικές μονάδες αντικαθιστούν τις κεφαλίδες, πώς να δηλώνετε και να εξάγετε λειτουργικές μονάδες, κατατμήσεις λειτουργιών και τη διαδρομή μετάβασης από κώδικα που βασίζεται σε κεφαλίδες.
Γιατί οι ενότητες αντικαθιστούν τις κεφαλίδες
Το σύστημα#includeείναι το θεμέλιο της μεταγλώττισης C++ για δεκαετίες, αλλά φέρει σοβαρά προβλήματα. Οι κεφαλίδες περιλαμβάνονται με κείμενο — ο προεπεξεργαστής αντιγράφει ολόκληρο το αρχείο σε κάθε μονάδα μετάφρασης. Αυτό σημαίνει ότι το#include μπορεί να επικολλήσει 50.000+ γραμμές κώδικα σε κάθε αρχείο.cpp. Το αποτέλεσμα: αργή μεταγλώττιση, ευθραυστή περιλαμβάνει την παραγγελία (οι μακροεντολές από μια κεφαλίδα επηρεάζουν μια άλλη) και Παραβάσεις ενός κανόνα ορισμού (ODR) που προκαλούν αθόρυβη απροσδιόριστη συμπεριφορά.
Οι μονάδες C++20 λύνουν όλα αυτά τα προβλήματα. Μια λειτουργική μονάδα μεταγλωττίζεται μία φορά σε μια δυαδική διεπαφή και στη συνέχεια εισάγεται από τους καταναλωτές χωρίς εκ νέου ανάλυση. Δεν υπάρχει συμπερίληψη κειμένου, καμία διαρροή μακροεντολών και καμία εξάρτηση συμπερίληψης σειράς. Οι χρόνοι κατασκευής μπορούν να βελτιωθούν κατά 5-20 φορές για μεγάλα έργα.
Ενότητα Δήλωση και Εξαγωγή
Μια μονάδα έχει μια μονάδα διεπαφής (που δηλώνει τι εξάγεται) και προαιρετικά μία ή περισσότερες μονάδες υλοποίησης. Η λέξη-κλειδί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εισαγωγή std (C++23)
Το C++23 εισάγει τοimport 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, το GCC, το 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 XP)1. Ποιο είναι το κύριο πλεονέκτημα των λειτουργικών μονάδων έναντι του #include;
2. Τι συμβαίνει σε μια λειτουργία που ορίζεται σε μια λειτουργική μονάδα αλλά ΔΕΝ επισημαίνεται με`export`;
3. Τι κάνει το`import std;`στο C++23;