सामग्री पर जाएं

C++20 मॉड्यूल

जानें कि मॉड्यूल हेडर की जगह क्यों लेते हैं, मॉड्यूल को कैसे डिक्लेयर और एक्सपोर्ट करें, मॉड्यूल पार्टिशन क्या हैं, और हेडर-आधारित कोड से बदलाव का रास्ता क्या है।

मॉड्यूल हेडर की जगह क्यों लेते हैं

#include सिस्टम दशकों से C++ कंपाइलेशन की बुनियाद रहा है, लेकिन इसमें गंभीर समस्याएँ हैं। हेडर टेक्स्ट के रूप में शामिल किए जाते हैं — प्रीप्रोसेसर पूरी फ़ाइल को हर ट्रांसलेशन यूनिट में कॉपी कर देता है। इसका मतलब है कि #include हर .cpp फ़ाइल में 50,000+ पंक्तियों का कोड चिपका सकता है। नतीजा: धीमा कंपाइलेशन, नाज़ुक include क्रम (एक हेडर के मैक्रो का दूसरे पर असर पड़ना), और वन डेफ़िनिशन रूल (ODR) के उल्लंघन जो चुपचाप अपरिभाषित व्यवहार पैदा करते हैं।

C++20 मॉड्यूल इन सभी समस्याओं को हल करते हैं। एक मॉड्यूल एक बार बाइनरी इंटरफ़ेस में कंपाइल होता है, फिर उपयोगकर्ता उसे दोबारा पार्स किए बिना import करते हैं। इसमें न टेक्स्ट के रूप में शामिल करना है, न मैक्रो का रिसाव, और न include-क्रम पर निर्भरता। बड़े प्रोजेक्ट में बिल्ड टाइम 5-20 गुना तक बेहतर हो सकता है।

मॉड्यूल डिक्लेरेशन और एक्सपोर्ट

एक मॉड्यूल में एक इंटरफ़ेस यूनिट (जो बताती है कि क्या एक्सपोर्ट हो रहा है) और वैकल्पिक रूप से एक या अधिक इम्प्लीमेंटेशन यूनिट होती हैं। export कीवर्ड डिक्लेरेशन को import करने वालों के लिए दिखाई देने योग्य बनाता है:

math_utils.cppm
// 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
// 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

मॉड्यूल पार्टिशन

बड़े मॉड्यूल को पार्टिशन में बाँटा जा सकता है — ये उप-मॉड्यूल हैं जो मिलकर पूरा मॉड्यूल इंटरफ़ेस बनाते हैं। पार्टिशन मॉड्यूल के आंतरिक होते हैं और बाहरी उपयोगकर्ता उन्हें सीधे import नहीं कर सकते:

module_partitions.cppm
// --- 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

import std (C++23)

C++23 import std; लाता है, जो पूरी स्टैंडर्ड लाइब्रेरी को एक मॉड्यूल के रूप में import करता है। कंपाइलेशन की गति के लिए यह गेम-चेंजर है:

import std;  // सब कुछ: 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; दर्जनों #include निर्देशों की जगह एक ही import ले लेता है, जो काफ़ी तेज़ी से कंपाइल होता है क्योंकि स्टैंडर्ड लाइब्रेरी मॉड्यूल पहले से कंपाइल किया हुआ होता है। import std.compat; भी है, जो अतिरिक्त रूप से C लाइब्रेरी के नाम (जैसे printf, strlen) ग्लोबल नेमस्पेस में उपलब्ध कराता है।

सामान्य गलती

कंपाइलर सपोर्ट अभी परिपक्व हो रहा है: 2024 तक GCC, Clang और MSVC तीनों मॉड्यूल को सपोर्ट करते हैं, लेकिन पूर्णता के स्तर और बिल्ड सिस्टम इंटीग्रेशन अलग-अलग हैं। MSVC का सपोर्ट सबसे परिपक्व है।

बिल्ड सिस्टम इंटीग्रेशन: CMake 3.28+ CXX_MODULE_SETS फ़ीचर के साथ मॉड्यूल को सपोर्ट करता है, लेकिन इकोसिस्टम (डिपेंडेंसी स्कैनर, पैकेज मैनेजर) अभी पकड़ बना रहा है। अब बिल्ड का क्रम मायने रखता है — मॉड्यूल इंटरफ़ेस को उनके उपयोगकर्ताओं से पहले कंपाइल करना होगा।

मैक्रो एक्सपोर्ट नहीं: मॉड्यूल जानबूझकर #define मैक्रो एक्सपोर्ट नहीं करते। जो कोड हेडर के मैक्रो (जैसे WIN32_LEAN_AND_MEAN या NOMINMAX) पर निर्भर है, उसे इसे अलग तरीक़े से संभालना होगा।

माइग्रेशन रणनीति: स्टैंडर्ड लाइब्रेरी के include की जगह import std; (C++23) से शुरुआत करें, फिर धीरे-धीरे अपने कोड को मॉड्यूल में बदलें, और उन लीफ़ लाइब्रेरी से शुरू करें जिनकी डिपेंडेंसी कम हैं।

सर्वोत्तम अभ्यास

मॉड्यूल अपनाते समय:

1. मॉड्यूल इंटरफ़ेस को छोटा रखें — केवल वही एक्सपोर्ट करें जो उपयोगकर्ताओं को चाहिए। जो एंटिटी एक्सपोर्ट नहीं हैं उनमें मॉड्यूल लिंकेज होता है और वे ODR उल्लंघन नहीं कर सकतीं।
2. बड़े मॉड्यूल के लिए पार्टिशन उपयोग करें — उन्हें तार्किक उप-मॉड्यूल में बाँटें (जैसे :types, :algorithms, :io)।
3. export namespace को प्राथमिकता दें — इंटरफ़ेस को व्यवस्थित रखने के लिए अलग-अलग डिक्लेरेशन के बजाय पूरे नेमस्पेस को एक्सपोर्ट करें।
4. एक ही लाइब्रेरी के लिए #include और import को न मिलाएँ — एक चुनें और एक ट्रांसलेशन यूनिट के भीतर उसी पर टिके रहें।
5. मॉड्यूल इंटरफ़ेस फ़ाइलों के लिए .cppm या .ixx एक्सटेंशन उपयोग करें ताकि बिल्ड सिस्टम उन्हें अपने-आप पहचान सकें।

मुख्य बातें
  • मॉड्यूल #include की जगह import लाते हैं — टेक्स्ट के रूप में शामिल करना नहीं, मैक्रो का रिसाव नहीं, include-क्रम के बग नहीं
  • डिक्लेरेशन को import करने वालों के लिए दिखाई देने योग्य बनाने के लिए export का उपयोग करें; जो एंटिटी एक्सपोर्ट नहीं हैं वे मॉड्यूल-प्राइवेट होती हैं
  • मॉड्यूल पार्टिशन बड़े मॉड्यूल को उप-घटकों में बाँटते हैं, जबकि एकीकृत इंटरफ़ेस प्रस्तुत करते हैं
  • import std; (C++23) सभी स्टैंडर्ड लाइब्रेरी include की जगह एक ही तेज़ import ले लेता है
  • बिल्ड सिस्टम को मॉड्यूल इंटरफ़ेस उनके उपयोगकर्ताओं से पहले कंपाइल करने होंगे — कंपाइलेशन का क्रम मायने रखता है

क्विज़ — अपना ज्ञान परखें

(15 XP)

1. #include की तुलना में मॉड्यूल का मुख्य लाभ क्या है?

2. किसी मॉड्यूल में परिभाषित फ़ंक्शन का क्या होता है जिस पर `export` नहीं लगा है?

3. C++23 में `import std;` क्या करता है?