परिचय: जब C++ "सिर्फ एक भाषा" नहीं रह जाता
\n\nआपने पहले ही दर्जनों कंसोल प्रोग्राम लिखे हैं, std::vector का उपयोग करना सीख लिया है, और टेम्पलेट्स को भी समझ लिया है। लेकिन C++ एक ऐसी भाषा है जो सतही दृष्टिकोण को कभी माफ नहीं करती। एक शुरुआती डेवलपर ठीक उसी क्षण अगले स्तर पर पहुँचता है जब उसका सामना अनिर्धारित व्यवहार (UB) से होता है और वह समझने लगता है कि कंपाइलर कोई दयालु मित्र नहीं, बल्कि एक सख्त अनुबंधकर्ता है।
इस लेख में हम भूला हुआ ; या = और == के बीच भ्रम जैसी सामान्य गलतियों पर चर्चा नहीं करेंगे। हम उन जालों के बारे में बात करेंगे जो उन लोगों की प्रतीक्षा करते हैं जो सिंटैक्स तो जानते हैं, लेकिन भाषा के दर्शन में अभी महारत हासिल नहीं की है। ये गलतियाँ औद्योगिक कोड में 90% मुश्किल से पकड़ में आने वाले बग्स का कारण हैं।
1. Rule of Five और Rule of Zero की अनदेखी
\n\nसमस्या: अंतर्निहित रूप से जनरेट की गई विशेष सदस्य फ़ंक्शन
\n\nJava या Python से आए कई डेवलपर्स मानते हैं कि कंपाइलर ऑब्जेक्ट्स की कॉपी और मूविंग के लिए "सब कुछ अपने आप सही कर देगा"। यह एक खतरनाक भ्रांति है।
\n\nडायनामिक रिसोर्स वाले एक क्लास पर विचार करें:
\n\nclass Buffer {\npublic:\n Buffer(size_t size) : data(new char[size]), size(size) {}\n ~Buffer() { delete[] data; }\n \n // सब कुछ ठीक है? नहीं! कोई कॉपी कंस्ट्रक्टर और असाइनमेंट ऑपरेटर नहीं!\nprivate:\n char* data;\n size_t size;\n};\n\nvoid process() {\n Buffer a(100);\n Buffer b = a; // शैलो कॉपी! एक ही मेमोरी पर दो पॉइंटर्स।\n} // डबल फ्री — UB!\n\nRule of Five कहता है: यदि आपने पाँच विशेष विधियों में से कम से कम एक को परिभाषित किया है (डिस्ट्रक्टर, कॉपी कंस्ट्रक्टर, कॉपी असाइनमेंट ऑपरेटर, मूव कंस्ट्रक्टर, मूव असाइनमेंट ऑपरेटर), तो आपको उन सभी को परिभाषित करना होगा।
\n\nसमाधान: संसाधनों के प्रबंधन के लिए std::unique_ptr या std::vector का उपयोग करें। यह Rule of Zero दृष्टिकोण है: यदि आपका क्लास सीधे संसाधनों का प्रबंधन नहीं करता है, तो कंपाइलर स्वयं सही विशेष फ़ंक्शन जनरेट करेगा।
class Buffer {\npublic:\n Buffer(size_t size) : data(std::make_unique<char[]>(size)), size(size) {}\n // Rule of Zero — डिस्ट्रक्टर, कॉपी और मूव सही ढंग से काम करते हैं!\nprivate:\n std::unique_ptr<char[]> data;\n size_t size;\n};\n\n2. मूव-सिमैंटिक्स का गलत उपयोग
\n\nसमस्या: मूव ऑब्जेक्ट को अपने आप "खाली" नहीं बनाता
\n\nसबसे कपटी गलतियों में से एक यह विश्वास है कि std::move के बाद मूल ऑब्जेक्ट उपयोग के लिए "सुरक्षित" हो जाता है। वास्तव में std::move केवल rvalue-रेफरेंस में परिवर्तित करना है। मूव किए गए ऑब्जेक्ट की स्थिति कार्यान्वयन पर निर्भर करती है।
std::string a = "Hello, World!";\nstd::string b = std::move(a);\nstd::cout << a; // तकनीकी रूप से — UB? नहीं, लेकिन a की सामग्री अनिर्धारित है!\n// हो सकता है खाली स्ट्रिंग, या हो सकता है — "Hello, World!"\n\nमानक केवल यह गारंटी देता है कि मूव किया गया ऑब्जेक्ट सही, लेकिन अनिर्धारित स्थिति में है। आप a.clear() या a = "new value" कॉल कर सकते हैं, लेकिन आपको इसकी सामग्री पर भरोसा नहीं करना चाहिए।
सुझाव: मूव करने के बाद, या तो ऑब्जेक्ट को ज्ञात स्थिति में रीसेट करें, या इसका उपयोग बंद कर दें। उपयोगकर्ता-परिभाषित प्रकारों के लिए, हमेशा मूव-कंस्ट्रक्टर की पोस्टकंडीशन का दस्तावेजीकरण करें।
\n\n3. रॉ पॉइंटर्स और एक्सेप्शन के माध्यम से मेमोरी लीक
\n\nसमस्या: एक्सेप्शन संसाधन मुक्ति की श्रृंखला को "तोड़" देते हैं
\n\nअनुभवी डेवलपर्स भी कभी-कभी मैन्युअल मेमोरी प्रबंधन वाला कोड लिखते हैं:
\n\nvoid process() {\n int* ptr1 = new int(42);\n int* ptr2 = new int(100);\n \n // यदि यहाँ एक्सेप्शन आता है — ptr1 का लीक!\n dangerous_function();\n \n delete ptr2;\n delete ptr1;\n}\n\nC++ में finally नहीं है