परिचय: यह क्यों महत्वपूर्ण है?
\n\nक्या आपने कभी सोचा है कि C++ में वेक्टर के अंत में एक तत्व जोड़ने के दो तरीके क्यों हैं? ऐसा लगता है, अगर पहले से सिद्ध push_back मौजूद है तो पहिया का पुनः आविष्कार क्यों करें? इसका उत्तर आधुनिक C++ के प्रदर्शन और लचीलेपन में छिपा है।
Stack Overflow पर इस प्रश्न ने हजारों बार देखा गया और सैकड़ों टिप्पणियाँ प्राप्त की हैं। और यह कोई संयोग नहीं है: push_back और emplace_back के बीच अंतर को समझना केवल एक शैक्षणिक ज्ञान नहीं है, बल्कि एक व्यावहारिक कौशल है जो आपके कोड को 2-3 गुना तेज कर सकता है। आइए इसे एक बार और हमेशा के लिए समझ लें।
मूलभूत अंतर: दस्तावेज़ क्या कहते हैं?
\n\nआइए सरल से शुरू करें। दोनों विधियाँ कंटेनर (वेक्टर, सूची, कतार) के अंत में एक तत्व जोड़ती हैं। लेकिन वे इसे अलग-अलग तरीके से करती हैं:
\n\n- \n
push_back— एक तैयार ऑब्जेक्ट (मान, संदर्भ या rvalue-संदर्भ द्वारा) लेता है और इसे कंटेनर में कॉपी/मूव करता है। \n emplace_back— ऑब्जेक्ट के कंस्ट्रक्टर के लिए आर्गुमेंट लेता है और इसे सीधे कंटेनर के अंदर बनाता है (in-place construction)। \n
अमूर्त लगता है? आइए कोड देखें।
\n\nव्यावहारिक उदाहरण: एक क्लास बनाना
\n\nमान लीजिए हमारे पास एक सरल क्लास Person है:
#include <iostream>\n#include <vector>\n#include <string>\n\nclass Person {\npublic:\n std::string name;\n int age;\n\n // कंस्ट्रक्टर\n Person(const std::string& n, int a) \n : name(n), age(a) {\n std::cout << "कंस्ट्रक्टर " << name << " के लिए कॉल किया गया" << std::endl;\n }\n\n // कॉपी कंस्ट्रक्टर\n Person(const Person& other) \n : name(other.name), age(other.age) {\n std::cout << "कॉपी कंस्ट्रक्टर " << name << " के लिए" << std::endl;\n }\n\n // मूव कंस्ट्रक्टर\n Person(Person&& other) noexcept\n : name(std::move(other.name)), age(other.age) {\n std::cout << "मूव कंस्ट्रक्टर " << name << " के लिए" << std::endl;\n }\n};\n\nअब देखते हैं कि विभिन्न विधियों का उपयोग करने पर क्या होता है।
\n\nविकल्प 1: अस्थायी ऑब्जेक्ट के साथ push_back
\n\nint main() {\n std::vector<Person> people;\n people.push_back(Person("Alice", 25));\n return 0;\n}\n\nआउटपुट:
\nकंस्ट्रक्टर Alice के लिए कॉल किया गया\nमूव कंस्ट्रक्टर Alice के लिए\n\nक्या हुआ? पहले एक अस्थायी Person ऑब्जेक्ट बनाया गया (कंस्ट्रक्टर का पहला कॉल)। फिर यह ऑब्जेक्ट वेक्टर में मूव हो गया (मूव कंस्ट्रक्टर)। दो कॉल — दोगुना काम।
विकल्प 2: तैयार ऑब्जेक्ट के साथ push_back
\n\nint main() {\n std::vector<Person> people;\n Person alice("Alice", 25);\n people.push_back(alice); // alice — lvalue\n return 0;\n}\n\nआउटपुट:
\nकंस्ट्रक्टर Alice के लिए कॉल किया गया\nकॉपी कंस्ट्रक्टर Alice के लिए\n\nयहाँ हम मूव के बजाय कॉपी देखते हैं। यदि ऑब्जेक्ट बड़ा है (जैसे, डायनामिक ऐरे शामिल हैं), तो कॉपी करना महंगा हो सकता है।
\n\nविकल्प 3: emplace_back
\n\nint main() {\n std::vector<Person> people;\n people.emplace_back("Bob", 30); // कंस्ट्रक्टर के लिए आर्गुमेंट पास करें\n return 0;\n}\n\nआउटपुट:
\nकंस्ट्रक्टर Bob के लिए कॉल किया गया\n\nकंस्ट्रक्टर का केवल एक कॉल! Person ऑब्जेक्ट सीधे वेक्टर की मेमोरी में बनाया जाता है, बिना किसी मध्यवर्ती कॉपी या मूव के।
प्रदर्शन: अंतर कब महत्वपूर्ण है?
\n\nसरल प्रकारों (int, double, पॉइंटर्स) के लिए अंतर नगण्य है — कंपाइलर सब कुछ ऑप्टिमाइज़ कर देता है। लेकिन जटिल ऑब्जेक्ट्स के लिए अंतर महत्वपूर्ण हो सकता है:
\n\n- \n
- भारी कॉपी कंस्ट्रक्टर वाले ऑब्जेक्ट्स (जैसे,
std::string, कंटेनर