C++ मैक्रोज़ का रहस्य: मैक्रोज़ में do-while(0) और if-else की आवश्यकता क्यों है और 3 घातक गलतियाँ

शुरुआती लोगों के लिए ऑनलाइन Python प्रशिक्षक

Python आसानी से सीखें बिना सिद्धांत के बोझ के। स्वचालित जांच के साथ व्यावहारिक कार्य हल करें और ब्राउज़र में सीधे कोड लिखें।

पाठ्यक्रम शुरू करें

C++ में मैक्रोज़: खतरनाक सुंदरता या आवश्यक बुराई?

\n\n

हर कोई जिसने कभी C++ में लिखा है, उसने मैक्रोज़ का सामना किया है। एक तरफ, यह मेटाप्रोग्रामिंग के लिए एक शक्तिशाली उपकरण है जो टेम्पलेट कोड की मात्रा को कम कर सकता है। दूसरी तरफ, मैक्रोज़ अपनी चालाकी के लिए प्रसिद्ध हैं। बस एक अर्धविराम छोड़ दें या कोष्ठक गलत तरीके से रखें, और कोड एक अप्रत्याशित राक्षस में बदल जाता है।

\n\n

लेकिन C++ की दुनिया में मैक्रोज़ की एक विशेष श्रेणी है जो स्पष्ट रूप से अजीब लगती है। क्या आपने कभी ऐसा कोड देखा है?

\n\n
#define SAFE_DELETE(ptr) do { delete (ptr); (ptr) = nullptr; } while(0)
\n\n

यह तार्किक लगता है: पॉइंटर को हटाएं और इसे शून्य करें। लेकिन यहाँ इस अर्थहीन do-while(0) की आवश्यकता क्यों है? एक लूप जो गारंटी के साथ केवल एक बार चलता है? यह बेतुकेपन की हद तक अतिरेक जैसा दिखता है। या if-else के साथ एक और उदाहरण:

\n\n
#define LOG_ERROR(msg) if (g_logging) std::cerr << (msg)
\n\n

लगता है सब कुछ सरल है। लेकिन यहीं वे रेक छिपे हैं जिन पर हजारों डेवलपर्स ठोकर खा चुके हैं। Stack Overflow पर इस प्रश्न को दसियों हज़ार बार देखा गया है, और आज हम समझेंगे कि ऐसी संरचनाएँ बग नहीं बल्कि एक विशेषता क्यों हैं, और वे किन C++ मैक्रो त्रुटियों को रोकती हैं।

\n\n

मैक्रोज़ फ़ंक्शन क्यों नहीं हैं? मुख्य समस्या

\n\n

do-while(0) की गहराई में जाने से पहले, आइए याद करें कि मैक्रो एक सामान्य फ़ंक्शन से कैसे भिन्न होता है। C++ में, मैक्रो एक टेक्स्टुअल प्रतिस्थापन है। प्रीप्रोसेसर केवल मैक्रो के बॉडी को उस स्थान पर कॉपी करता है जहाँ इसे कॉल किया गया है। कोई टाइप चेक नहीं, कोई स्कोप नहीं - केवल स्ट्रिंग रिप्लेसमेंट।

\n\n

यही कारण है कि क्लासिक C++ मैक्रो त्रुटियाँ उत्पन्न होती हैं। एक सरल उदाहरण देखें:

\n\n
#define SQUARE(x) x * x\n\nint main() {\n    int a = 5;\n    int result = SQUARE(a + 1); // 36 की उम्मीद, 11 मिलता है\n    std::cout << result; // 11 प्रिंट करेगा\n    return 0;\n}
\n\n

क्या हुआ? मैक्रो a + 1 * a + 1 में विस्तारित हुआ। ऑपरेटर प्राथमिकता के कारण, गुणा जोड़ से पहले हुआ। पहली गलती - कोष्ठकों की कमी। इसे ठीक करें:

\n\n
#define SQUARE(x) ((x) * (x))
\n\n

अब यह काम करता है। लेकिन यह तो केवल शुरुआत है। असली समस्याएँ तब शुरू होती हैं जब मैक्रो में कई स्टेटमेंट या कंट्रोल स्ट्रक्चर होते हैं।

\n\n

do-while(0) इडियम: सिंटैक्स आपदाओं से ढाल

\n\n

अपने SAFE_DELETE पर वापस आते हैं। इस अजीब लूप के बिना मैक्रो को सरल क्यों नहीं लिखा जाए?

\n\n
#define SAFE_DELETE(ptr) delete (ptr); (ptr) = nullptr;
\n\n

यह तार्किक लगता है। लेकिन कल्पना करें कि आप इसे इस संदर्भ में उपयोग करते हैं:

\n\n
if (ptr != nullptr)\n    SAFE_DELETE(ptr);\nelse\n    std::cout << "Pointer is null";
\n\n

प्रतिस्थापन के बाद, प्रीप्रोसेसर इसे इसमें बदल देगा:

\n\n
if (ptr != nullptr)\n    delete (ptr);\n    (ptr) = nullptr;\nelse\n    std::cout << "Pointer is null";
\n\n

और यहाँ बग है! else अब if से संबंधित नहीं है, क्योंकि उनके बीच एक दूसरा ऑपरेटर आ गया है। कंपाइलर एक त्रुटि देगा, लेकिन बात स्पष्ट है: मैक्रो ने कंट्रोल स्ट्रक्चर को "तोड़" दिया। ऐसे मामलों के लिए ही do-while(0) की आवश्यकता है। यह कई ऑपरेटरों को एक समग्र में बदल देता है, तर्क को संरक्षित करते हुए:

\n\n
if (ptr != nullptr)\n    do { delete (ptr); (ptr) = nullptr; } while(0);\nelse\n    std::cout << "Pointer is null";
\n\n

अब सब कुछ सही ढंग से काम करता है। मैक्रो इडियम do-while(0) एक मानक तकनीक है जो गारंटी देती है कि मैक्रो एक एकल स्टेटमेंट की तरह व्यवहार करेगा।

\n\n

केवल { } ब्लॉक के बजाय do-while(0) ही क्यों?

\n\n

अच्छा प्रश्न! आखिरकार, केवल कर्ली ब्रेसेस का उपयोग किया जा सकता था:

\n\n
#define SAFE_DELETE(ptr) { delete (ptr); (ptr) = nullptr; }
\n\n

आइए उपयोग करने का प्रयास करें

ब्लॉग

पुस्तक अनुशंसाएँ