परिचय: दस्तावेज़ क्या नहीं बताते
\n\nकल्पना करें: आप एक उच्च-लोड सर्वर या डेटा प्रोसेसिंग एल्गोरिदम लिख रहे हैं। आपके पास दो std::vector<int> हैं, और आपको उन्हें एक में मर्ज करना है। लगता है, इससे आसान क्या हो सकता है? बस लो और जोड़ो। लेकिन C++ सतही दृष्टिकोण को माफ नहीं करता। एक गलत तरीका — और आपको अनावश्यक प्रतियां, प्रदर्शन में गिरावट, या यहां तक कि अपरिभाषित व्यवहार मिलता है।
Stack Overflow पर «Appending a vector to a vector» प्रश्न ने हजारों बार देखा गया है, यह संयोग नहीं है। अनुभवी डेवलपर्स भी कभी-कभी push_back को insert समझ लेते हैं या move-सिमैंटिक्स भूल जाते हैं। इस लेख में हम कंपाइलर के अंदर झांकेंगे और समझेंगे कि एक वेक्टर को दूसरे में जोड़ना वास्तव में कैसे काम करता है। आप जानेंगे कि क्यों std::copy insert से तेज़ हो सकता है, और कब move-सिमैंटिक्स आपके प्रोजेक्ट को बचाएगा।
सबसे स्पष्ट (और खतरनाक) तरीका: लूप में push_back
\n\nशुरुआती अक्सर ऐसा लिखते हैं:
\n\nstd::vector<int> dest = {1, 2, 3};\nstd::vector<int> src = {4, 5, 6};\n\nfor (size_t i = 0; i < src.size(); ++i) {\n dest.push_back(src[i]);\n}\n\nयहाँ क्या हो रहा है? प्रत्येक पुनरावृत्ति पर push_back जाँचता है कि पर्याप्त मेमोरी आवंटित है या नहीं। यदि नहीं, तो पुनःआवंटन होता है: पुरानी ऐरे को मेमोरी के नए क्षेत्र में कॉपी किया जाता है, पुरानी हटा दी जाती है। तीन तत्वों के लिए, 2-3 पुनःआवंटन हो सकते हैं। छोटे वेक्टर के लिए यह अदृश्य है, लेकिन हजारों तत्वों के लिए — यह आपदा है।
समस्या: O(n) जाँच और पूरी ऐरे की संभावित प्रतिलिपियाँ। C++17 और नए में, कंपाइलर अनुकूलन कर सकता है, लेकिन इस पर भरोसा करना जोखिम भरा है।
\n\npush_back के अंदर क्या होता है?
\n- \n
size() < capacity()की जाँच। \n - यदि नहीं, तो
reallocate(new_capacity)को कॉल करें। \n - मौजूदा तत्वों की प्रतिलिपि (या स्थानांतरण)। \n
- पुराने बफर को हटाना। \n
- नए तत्व की प्रतिलिपि। \n
निष्कर्ष: पूरे वेक्टर को जोड़ने के लिए कभी भी लूप का उपयोग न करें, जब तक कि आप जानबूझकर थोड़ी संख्या में तत्वों के लिए ऐसा नहीं कर रहे हों।
\n\nसही तरीका: std::vector::insert
\n\nमानक पुस्तकालय एक सुरुचिपूर्ण समाधान प्रदान करता है:
\n\nstd::vector<int> dest = {1, 2, 3};\nstd::vector<int> src = {4, 5, 6};\n\ndest.insert(dest.end(), src.begin(), src.end());\n\nएक पंक्ति! लेकिन इस कॉल के पीछे क्या छिपा है? insert निम्नलिखित करता है:
- \n
- आवश्यक मेमोरी की गणना:
dest.size() + src.size()। \n - यदि वर्तमान क्षमता अपर्याप्त है — तो आवश्यकतानुसार (या रिजर्व के साथ, कार्यान्वयन पर निर्भर करता है) आवंटित करता है। \n
- मौजूदा तत्वों को स्थानांतरित करता है (यदि अंत में नहीं डाल रहे हैं)। \n
srcसे नए तत्वों की प्रतिलिपि (या स्थानांतरण) करता है। \n
महत्वपूर्ण: insert एक पुनःआवंटन करता है (या कोई नहीं, यदि जगह पर्याप्त है)। यह लूप से बेहतर है, लेकिन फिर भी src से तत्वों की प्रतिलिपि होती है।
insert का प्रदर्शन
\nयदि सम्मिलन के बाद src की आवश्यकता नहीं है, तो आप std::make_move_iterator का उपयोग करके इसके तत्वों को स्थानांतरित कर सकते हैं:
dest.insert(dest.end(),\n std::make_move_iterator(src.begin()),\n std::make_move_iterator(src.end()));\n\nयह प्रतिलिपि को स्थानांतरण में बदल देता है। std::vector<int> के लिए कोई अंतर नहीं है (int एक तुच्छ प्रकार है), लेकिन std::vector<std::string> या जटिल वस्तुओं के लिए, स्थानांतरण प्रतिलिपि की तुलना में बहुत सस्ता है।
विकल्प: back_inserter के साथ std::copy
\n\nकुछ डेवलपर्स STL एल्गोरिदम पसंद करते हैं:
\n\nब्लॉग
पुस्तक अनुशंसाएँ
यह भी पढ़ें