मैंने पिछले चार वर्षों में इस सवाल के बारे में काफी सोचा है। मैं इस निष्कर्ष पर पहुंचा हूं कि 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 में अपग्रेड करने की सफलता के रूप में देखा जाता। इसके बजाय, मैंने बग को मास्क किया और इसे महीनों बाद तक नहीं पाया।