अंतिम अद्यतन:2026-10-12
C++20 Modules मॉड्यूल¶
जब हम अपने लिखे header files या third-party लाइब्रेरी का उपयोग करते हैं, तो प्रायः #include directive से लाइब्रेरी शामिल करते हैं, यह सबके द्वारा आम तौर पर उपयोग किया जाने वाला तरीका है। यह तरीका वास्तव में एक source file (header file) के सारे कोड को दूसरी फ़ाइल में copy करता है।
तो, इससे निम्नलिखित समस्याएँ आती हैं:
- Source file एक ही target में कई बार include हो सकती है, इसलिए हम प्रायः
#pragma onceया#ifndefका उपयोग करते हैं, ताकि source file एक ही translation unit में कई बार include न हो। - कोड की copy से compile समय लंबा होता है, एक header file बदलते ही, उसे indirectly include करने वाली कुछ फ़ाइलें दोबारा compile हो जाती हैं।
#includeके क्रम की समस्या, कभी-कभी अजीबोगरीब compile समस्याएँ आती हैं।
C++20 ने #include directive के विकल्प के रूप में एक नया तरीका पेश किया, जिसे module कहते हैं।
नीचे modules को गहराई से सीखते हैं।
1. मॉड्यूल units¶
C++ module एक या अधिक translation units (tu) से बनता है, जिनमें module घोषणा के लिए विशिष्ट कीवर्ड होते हैं। ऐसे translation unit को module unit कहते हैं।
गैर-मॉड्यूल unit वाले translation units को global module का हिस्सा माना जाता है, global module गुमनाम है, इसका कोई interface नहीं है, और इसमें सामान्य गैर-मॉड्यूल कोड होता है।
1.1 Global Module Fragment¶
मॉड्यूल unit global module fragment को prefix की तरह रख सकता है, जब header file import न हो सके (खासकर जब header file preprocessing macros को configuration की तरह उपयोग करती हो), तो वह global module fragment सीधे पुराने कोड का उपयोग कर सकता है।
उदाहरण के लिए: नीचे दिए कोड में module; से export module Foo; के बीच global module fragment है।
module;
#include <iostream>
#ifdef Say
void hello();
#endif
export module Foo;
// purivew
void world();
एक बात ज़रूर ध्यान रखें: यदि इस module में global module fragment है, तो पहली घोषणा module; ही होनी चाहिए, यानी यह घोषणा कहीं और रखने पर त्रुटि होगी।
1.2 purview¶
purview को module का पूरा दायरा समझा जा सकता है। module घोषणा से शुरू होकर, translation unit के अंत तक फैला हुआ।
उदाहरण के लिए: hello इसमें नहीं है, world और GetData दोनों purview में हैं।
module;
void hello();
// <- 这里不在Foo模块的purview内
export module Foo;
// <- 在Foo模块的purview内
void world();
export void GetData();
1.3 Private module fragment¶
Primary module interface unit private module fragment को suffix की तरह रख सकता है, यह हिस्सा केवल primary module interface unit में हो सकता है, और यदि मौजूद हो, तो जिस module unit में यह है वह उस module का एकमात्र unit होना चाहिए। इसका उद्देश्य module के interface और क्रियान्वयन को एक ही translation unit में समेटना है, बिना क्रियान्वयन विवरण उजागर किए।
उदाहरण के लिए: मैं एक Shape बनाना चाहता हूँ, और उसका क्षेत्रफल निकालना चाहता हूँ।
बाहर की ओर केवल एक concrete Shape बनाने का interface उजागर करना है, साझा क्षेत्रफल गणना interface को कॉल करना है, इसलिए हम नीचे जैसा module लिख सकते हैं।
export module Shape;
export class Shape {
public:
virtual double CalculateArea() = 0;
};
export { std::shared_ptr<Shape> CreateRectangle(double length, double width); }
module :private; // here
class Rectangle : public Shape {
private:
double length;
double width;
public:
Rectangle(double l, double w) : length(l), width(w) {}
double CalculateArea() override { return length * width; }
};
std::shared_ptr<Shape> CreateRectangle(double length, double width) {
return std::make_shared<Rectangle>(length, width);
}
आंतरिक विवरण सब private में रख दें? मेरा g++ संस्करण 13 है, फिलहाल समर्थन नहीं है, यह त्रुटि देगा:
gcc का फिलहाल समर्थन यहाँ देखें
लोकल clang संस्करण 16 है, परीक्षण किया और सामान्य रूप से चल गया!
➜ clang++ -std=c++20 shape.cppm --precompile -o shape.pcm
➜ clang++ -std=c++20 shape.cc -fprebuilt-module-path=. shape.pcm -o shape
➜ ./shape
area is 2
ऊपर के तीन हिस्सों में, global और private module fragment module के अस्तित्व के लिए ज़रूरी नहीं हैं, purview module का अनिवार्य हिस्सा है।
2. मॉड्यूल उपयोग¶
2.1 मॉड्यूल बनाना¶
मॉड्यूल बनाना हमारे header file परिभाषित करने जैसा है, इसकी भी एक फ़ाइल होती है, प्रायः suffix .cppm होता है। हमें केवल इस फ़ाइल में export और module कीवर्ड का उपयोग करके, बाद में module का नाम लिखना है, इससे एक exportable module बन जाता है। उदाहरण के लिए:
export कीवर्ड को class, variable आदि जगहों पर उपयोग किया जा सकता है, प्रायः दो लेखन-शैलियाँ हैं:
एक तरीका export कीवर्ड को सामान्य घोषणा से पहले रखना, दूसरा तरीका export block है, namespace लेखन-शैली जैसा, जिसमें कई चीज़ें export की जा सकती हैं।
2.2 Export¶
यहाँ एक महत्वपूर्ण सवाल आता है, क्या export किया जा सकता है?
- variables, classes, structs, functions, namespaces, template functions/classes, concepts export किए जा सकते हैं
- internal linkage export नहीं हो सकती, जैसे static variables और functions, anonymous namespace।
- Export घोषणा namespace स्तर पर ही होनी चाहिए
namespace {
export void print_no_export() { // 匿名命名空间,不可导出
std::cout << "print no export" << std::endl;
}
};
namespace light {
export void print_export() { // ok
std::cout << "print export" << std::endl;
}
};
struct Foo {
export int a; // 不能导出成员变量
};
- Namespace export करने से उसकी सारी सामग्री implicitly export हो जाती है
export class Rectangle {
private:
double length;
double width;
public:
Rectangle(double l, double w) : length(l), width(w) {}
double CalculateArea() { return length * width; } // 隐式导出
};
export namespace {
void print_export() { // 隐式导出
std::cout << "print export" << std::endl;
}
};
export {
void func(); // 隐式导出
constexpr double PI{3.14}; // 隐式导出
};
- Export की गई entity की पहली घोषणा export घोषणा ही होनी चाहिए। बाद की घोषणाओं और परिभाषाओं में export कीवर्ड की ज़रूरत नहीं।
export class Foo; // ok
export class Foo; // ok,只是会冗余
class Foo { // ok,隐式export
public:
void print() { std::cout << "this is foo" << std::endl; }
};
class Bar; // not export
export class Bar; // 无效,第一个声明已经是不可导出,后续的不可导出
Foo f; // ok
f.print(); // ok
Bar b; // not ok
- गैर-exported मॉड्यूल export नहीं किया जा सकता
module foo;
export void print { } // error: 'export' may only occur after a module interface declaration
2.3 Import¶
इसके सम्मुख import है, import के भी कुछ नियम हैं, उदाहरण के लिए:
-
स्वयं को import नहीं कर सकते
-
मॉड्यूल unit में, सभी imports उस module unit की किसी भी घोषणा से पहले आने चाहिए। module unit के किसी भी बिंदु पर import नहीं किया जा सकता।
void func() {}
import shape; // error: post-module-declaration imports must be contiguous
---------------
import shape; // ok
void func() {}
- केवल global scope import की अनुमति है
- Circular import की अनुमति नहीं
2.4 मॉड्यूल में include¶
#include <iostream> का module में उपयोग कैसे करें?
2.4.1 purview क्षेत्र में #include¶
#include की जगह import का उपयोग करें
उदाहरण के लिए:
g++-13 से compile इस प्रकार, c++-system-header के बाद iostream लिखकर gcm compile किया जा सकता है।
g++-13 -std=c++20 -fmodules-ts -x c++-system-header iostream
g++-13 -fmodules-ts -std=c++20 -x c++ shape.cppm circle.cppm shape.cc
यदि अपनी header file हो, जैसे: consts.h, तो देखा कि उसे सीधे module के भीतर #include किया जा सकता है, उदाहरण के लिए:
2.4.2 Global Module Fragment क्षेत्र में #inlcude¶
#include और macros दोनों को सीधे इस क्षेत्र में उपयोग किया जा सकता है, उदाहरण के लिए:
3. मॉड्यूल विभाजन¶
जब हम किसी बड़े module को छोटे modules में तोड़ना चाहते हैं, तो निम्नलिखित दो तरीके अपना सकते हैं:
- Module partition।
- यानी यह हमें module को कई फ़ाइलों में तोड़ने की अनुमति देता है। हालांकि, यह उपयोगकर्ता के लिए वास्तव में अदृश्य है, उपयोग करते समय सामान्य रूप से module import करें।
- Submodule।
- यानी यह हमें बड़े module को कितने भी submodules के पदानुक्रम में तोड़ने की अनुमति देता है। उपयोगकर्ता पूरा module import कर सकता है, या केवल विशिष्ट submodule।
3.1 मॉड्यूल partition¶
सिंटैक्स:
उदाहरण के लिए: मेरे पास एक shape है, बाहर उपयोग के लिए केवल import shape करना है, फिर संबंधित interface कॉल करना है, यहाँ क्रमशः circle partition और rectangle partition के interface उपयोग हुए हैं।
shape के दो partition हैं, एक circle, एक rectangle, इसलिए module shape.cppm की सामग्री:
दोनों sub-partition की सामग्री:
// circle.cppm
module;
#include <iostream>
export module shape:circle;
export void DrawCircle() { std::cout << "draw circle" << std::endl; }
--------------
// rectangle.cppm
module;
#include <iostream>
export module shape:rectangle;
export void DrawRectangle() { std::cout << "draw rectangle" << std::endl; }
जब मैंने clang और g++ से compile किया तो पाया, clang-16 compile त्रुटि देता है, समर्थन नहीं।
g++-13 समर्थन करता है, compile करते समय sub-partition -> primary partition के क्रम में compile करने का ध्यान रखें, अन्यथा त्रुटि होगी।
➜ g++-13 -fmodules-ts -std=c++20 -x c++ shape.cppm circle.cppm rectangle.cppm shape.cc
shape:circle: error: failed to read compiled module: No such file or directory
shape:circle: note: compiled module file is 'gcm.cache/shape-circle.gcm'
shape:circle: note: imports must be built before being imported
shape:circle: fatal error: returning to the gate for a mechanical issue
ऐसा होना चाहिए:
3.2 Submodule¶
सिंटैक्स:
export module <module-name>.<sub_module-name>;
import <module-name>.<sub_module-name>; // 可以在import前面添加export导出该分区接口
ऊपर का उदाहरण submodule से भी क्रियान्वयन किया जा सकता है, हम shape को आज भी primary module रखते हैं, दो submodules क्रमशः circle और rectangle हैं।
// shape.cppm
export module shape;
export import shape.circle;
export import shape.rectangle;
---------------------
// rectangle.cppm
module;
#include <iostream>
export module shape.rectangle;
export void DrawRectangle() { std::cout << "draw rectangle" << std::endl; }
---------------------
// circle.cppm
module;
#include <iostream>
export module shape.circle;
export void DrawCircle() { std::cout << "draw circle" << std::endl; }
देखा जा सकता है कि उपयोग में module partition : का उपयोग करता है, जबकि submodule . का।
Call side का कोड module partition जैसा ही है।
हालांकि उपयोग के समय कुछ अंतर हैं, उदाहरण के लिए: जब sub-partition introduce किया जाता है, तो उसके interface से त्रुटि आती है: internal compiler error: Segmentation fault: 11, जबकि submodule सामान्य रूप से introduce होकर उपयोग हो सकता है।
// 引入子分区
import shape:circle;
int main() {
DrawCircle(); // error
return 0;
}
// 引入子模块
import shape.circle;
int main() {
DrawCircle(); // ok
return 0;
}
Submodule के लिए, import करते समय, primary module का नाम हटाया नहीं जा सकता, ऊपर primary partition में partition module introduce करते समय, हम :circle उपयोग कर सकते हैं, यहाँ .circle उपयोग नहीं कर सकते। त्रुटि इस प्रकार:
4. Interface और क्रियान्वयन¶
प्रायः कोड लिखते समय, हम कोड को "header file" और " क्रियान्वयन file" में बाँटते हैं, module के लिए, यह काम अब ज़रूरी नहीं। लेकिन फिर भी पुरानी लेखन-शैली का पालन किया जा सकता है।
उदाहरण के लिए: एक shape बनाना।
- Module shape.cppm परिभाषित करना
- Module shape.cc क्रियान्वयन
import shape;
import <iostream>;
Shape::Shape() {}
void Shape::Draw() { std::cout << "draw a shape" << std::endl; }
रोज़मर्रा में .h और .cpp के separation मॉडल जैसा ही है।
https://clang.llvm.org/docs/StandardCPlusPlusModules.html
https://timsong-cpp.github.io/cppwp/n4861/module.import#7.sentence-2
संबंधित विषय¶
- C++20 coroutines परिचय | C++那些事
- C++20 channel व्यवहारिक उपयोग | C++那些事
- C++20 Concepts अवधारणा | C++那些事
- C++20 constexpr और comma एक्सप्रेशन | C++那些事
- C++20 byte order endian | C++那些事
- C++20 Lambda एक्सप्रेशन (भाग 1) | C++那些事
- C++20 Ranges लाइब्रेरी | C++那些事
- C++20 std::span डिज़ाइन और जाल | C++那些事
- C++20 नई विशेषताओं की जानकारी | C++那些事
- C++20 [[likely]] और [[unlikely]] | C++那些事