मैंने पिछले चार वर्षों में इस सवाल के बारे में काफी सोचा है। मैं इस निष्कर्ष पर पहुंचा हूं कि push_backबनाम के बारे में अधिकांश स्पष्टीकरण emplace_backपूरी तस्वीर को याद करते हैं।
पिछले साल, मैंने C ++ 14 में टाइप डेडक्शन पर C ++ नाउ पर एक प्रस्तुति दी । मैं 13:49 पर push_backबनाम के बारे में बात करना शुरू करता हूं emplace_back, लेकिन उपयोगी जानकारी है जो इससे पहले कुछ सहायक सबूत प्रदान करती है।
वास्तविक प्राथमिक अंतर का निहितार्थ बनाम स्पष्ट निर्माणों के साथ क्या करना है। उस मामले पर विचार करें जहां हमारे पास एक एकल तर्क है जिसे हम पास करना चाहते हैं push_backया emplace_back।
std::vector<T> v;
v.push_back(x);
v.emplace_back(x);
आपके ऑप्टिमाइज़िंग कंपाइलर को इस पर अपना हाथ लगने के बाद, उत्पन्न कोड के संदर्भ में इन दोनों कथनों में कोई अंतर नहीं है। पारंपरिक ज्ञान यह है कि push_backएक अस्थायी वस्तु का निर्माण किया जाएगा, जो तब ले जाया जाएगा vजबकि emplace_backतर्क को आगे ले जाएगा और इसे बिना किसी प्रतिलिपि या चाल के साथ सीधे निर्माण करेगा। यह मानक पुस्तकालयों में लिखे गए कोड के आधार पर सही हो सकता है, लेकिन यह गलत धारणा बनाता है कि आपके द्वारा लिखे गए कोड को उत्पन्न करने के लिए अनुकूलन संकलक का काम है। ऑप्टिमाइज़र कंपाइलर की नौकरी वास्तव में आपके द्वारा लिखे गए कोड को उत्पन्न करने के लिए होती है यदि आप प्लेटफ़ॉर्म-विशिष्ट ऑप्टिमाइज़ेशन के विशेषज्ञ थे और रखरखाव, सिर्फ प्रदर्शन की परवाह नहीं करते थे।
इन दोनों कथनों के बीच वास्तविक अंतर यह है कि अधिक शक्तिशाली emplace_backकिसी भी प्रकार के निर्माणकर्ता को वहां से बाहर बुलाएगा, जबकि अधिक सतर्क push_backकेवल निर्माण करने वालों को बुलाएगा। अवैध निर्माण करने वाले सुरक्षित माने जाते हैं। यदि आप एक Uसे एक निर्माण कर सकते Tहैं, तो आप कह रहे हैं कि बिना किसी नुकसान के Uसभी जानकारी रख सकते हैं T। इसे पास करने के लिए किसी भी स्थिति में बहुत सुरक्षित है Tऔर यदि आप Uइसके बजाय बनाते हैं तो कोई भी बुरा नहीं मानेगा। अंतर्निहित निर्माता का एक अच्छा उदाहरण से रूपांतरण std::uint32_tहै std::uint64_t। एक अंतर्निहित रूपांतरण का एक बुरा उदाहरण है doubleकरने के लिए std::uint8_t।
हम अपनी प्रोग्रामिंग में सतर्क रहना चाहते हैं। हम शक्तिशाली सुविधाओं का उपयोग नहीं करना चाहते हैं क्योंकि यह सुविधा जितनी अधिक शक्तिशाली है, गलती से यह गलत या अप्रत्याशित रूप से आसान है। यदि आप स्पष्ट निर्माणकर्ताओं को बुलाने का इरादा रखते हैं, तो आपको बिजली की आवश्यकता है emplace_back। यदि आप केवल निहित निर्माणकर्ताओं को कॉल करना चाहते हैं, तो सुरक्षा के साथ रहें push_back।
एक उदाहरण
std::vector<std::unique_ptr<T>> v;
T a;
v.emplace_back(std::addressof(a)); // compiles
v.push_back(std::addressof(a)); // fails to compile
std::unique_ptr<T>से एक स्पष्ट रचनाकार है T *। क्योंकि emplace_backस्पष्ट निर्माणकर्ताओं को कॉल कर सकते हैं, एक गैर-मालिक का सूचक पास करना ठीक है। हालांकि, जब vगुंजाइश से बाहर हो जाता है, तो विध्वंसक deleteउस पॉइंटर पर कॉल करने का प्रयास करेगा , जिसे newइसके द्वारा आवंटित नहीं किया गया था क्योंकि यह सिर्फ एक स्टैक ऑब्जेक्ट है। इससे अपरिभाषित व्यवहार होता है।
यह केवल आविष्कारित कोड नहीं है। यह एक वास्तविक उत्पादन बग था जिसका मैंने सामना किया। कोड था std::vector<T *>, लेकिन यह सामग्री का स्वामित्व था। C ++ 11 के प्रवास के हिस्से के रूप में, मैंने यह संकेत T *देने के std::unique_ptr<T>लिए सही ढंग से बदल दिया कि वेक्टर अपनी स्मृति के स्वामित्व में था। हालांकि, मैं 2012 में मेरी समझ से दूर इन परिवर्तनों आधारित था, जिसके दौरान मैंने सोचा था कि "emplace_back सब कुछ push_back कर सकते हैं करता है और अधिक, तो क्यों मैं कभी push_back का प्रयोग करेंगे?", तो मैं भी बदल push_backलिए emplace_back।
अगर मैंने सुरक्षित रखने के रूप में कोड छोड़ दिया push_backहोता, तो मैं तुरंत इस लंबे समय तक चलने वाले बग को पकड़ लेता और इसे C ++ 11 में अपग्रेड करने की सफलता के रूप में देखा जाता। इसके बजाय, मैंने बग को मास्क किया और इसे महीनों बाद तक नहीं पाया।