मैं कभी भी emplace_back के बजाय push_back का उपयोग क्यों करूंगा?


231

C ++ 11 वैक्टर का नया कार्य है emplace_back। इसके विपरीत push_back, जो प्रतियों से बचने के लिए कंपाइलर ऑप्टिमाइजेशन पर निर्भर करता है, emplace_backकिसी ऑब्जेक्ट को इन-प्लेस बनाने के लिए कंस्ट्रक्टर को सीधे तर्क भेजने के लिए परफेक्ट फॉरवर्डिंग का उपयोग करता है। यह मुझे लगता है कि emplace_backसब कुछ push_backकर सकता है, लेकिन कुछ समय यह बेहतर करेगा (लेकिन कभी भी बदतर नहीं)।

मुझे किस कारण का उपयोग करना है push_back?

जवाबों:


161

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


यह मदद करेगा कि क्या आप इस बात पर विस्तार से बता सकते हैं कि आपके उदाहरण में ईकॉम क्या करता है, और यह गलत क्यों है।
एड्डी

4
@ जेडी: मैंने इसे समझाते हुए एक खंड जोड़ा: std::unique_ptr<T>इसमें एक स्पष्ट रचनाकार है T *। क्योंकि emplace_backस्पष्ट निर्माणकर्ताओं को कॉल कर सकते हैं, एक गैर-मालिक का सूचक पास करना ठीक है। हालांकि, जब vगुंजाइश से बाहर हो जाता है, तो विध्वंसक deleteउस पॉइंटर पर कॉल करने का प्रयास करेगा , जिसे newइसके द्वारा आवंटित नहीं किया गया था क्योंकि यह सिर्फ एक स्टैक ऑब्जेक्ट है। इससे अपरिभाषित व्यवहार होता है।
डेविड स्टोन

इसे पोस्ट करने के लिए धन्यवाद। मुझे इस बारे में पता नहीं था जब मैंने अपना उत्तर लिखा था, लेकिन अब मेरी इच्छा है कि मैं इसे तब लिखूं जब मैंने इसे बाद में सीख लिया था :) मैं वास्तव में उन लोगों को थप्पड़ मारना चाहता हूं जो नई सुविधाओं पर स्विच करते हैं बस हिप्पेस्ट काम करते हैं जो वे पा सकते हैं । दोस्तों, लोग C ++ 11 से पहले भी C ++ का उपयोग कर रहे थे, और इसके बारे में सब कुछ समस्याग्रस्त नहीं था। यदि आप नहीं जानते कि आप किसी विशेषता का उपयोग क्यों कर रहे हैं, तो इसका उपयोग न करें । तो खुशी है कि आपने इसे पोस्ट किया है और मुझे उम्मीद है कि इसे और अधिक बढ़ावा मिलेगा इसलिए यह मेरे ऊपर जाता है। +1
user541686

1
@ कैप्टनजैकस्पारो: ऐसा लग रहा है कि मैं निहित और स्पष्ट रूप से कह रहा हूं जहां मेरा मतलब है। आपने किस हिस्से को भ्रमित किया है?
डेविड स्टोन

4
@CaptainJacksparrow: एक explicitकंस्ट्रक्टर एक कंस्ट्रक्टर है जिस पर कीवर्ड explicitलागू होता है। एक "निहित" कंस्ट्रक्टर कोई भी कंस्ट्रक्टर है जिसमें वह कीवर्ड नहीं है। के std::unique_ptrकंस्ट्रक्टर के मामले में T *, std::unique_ptrउस कंस्ट्रक्टर ने लिखा था, लेकिन यहां मुद्दा यह है कि उस प्रकार के उपयोगकर्ता ने कॉल किया emplace_back, जिसने उस स्पष्ट कंस्ट्रक्टर को बुलाया। यदि ऐसा push_backहोता है, तो उस निर्माता को कॉल करने के बजाय, यह एक अंतर्निहित रूपांतरण पर निर्भर होता, जिसे केवल निहित निर्माता कह सकते हैं।
डेविड स्टोन

116

push_backहमेशा समान इनिशियलाइज़ेशन के उपयोग की अनुमति देता है, जो मुझे बहुत पसंद है। उदाहरण के लिए:

struct aggregate {
    int foo;
    int bar;
};

std::vector<aggregate> v;
v.push_back({ 42, 121 });

दूसरी ओर, v.emplace_back({ 42, 121 });काम नहीं करेगा।


58
ध्यान दें कि यह केवल एग्रीगेट इनिशियलाइज़ेशन और इनिशियलाइज़र-लिस्ट इनिशियलाइज़ेशन पर लागू होता है। यदि आप {}एक वास्तविक कंस्ट्रक्टर को कॉल करने के लिए सिंटैक्स का उपयोग कर रहे हैं , तो आप बस {}'s और उपयोग को हटा सकते हैं emplace_back
निकोल बोलस

गूंगा प्रश्न समय: इसलिए emplace_back का उपयोग सभी संरचितों के वैक्टर के लिए नहीं किया जा सकता है? या सिर्फ इस शैली के लिए शाब्दिक {42,121} का उपयोग नहीं कर रहे हैं?
फिल एच

1
@ ल्यूकैंटन: जैसा कि मैंने कहा, यह केवल एग्रीगेट और इनिशियलाइज़र-लिस्ट इनिशियलाइज़ेशन पर लागू होता है। आप {}वास्तविक रचनाकारों को कॉल करने के लिए सिंटैक्स का उपयोग कर सकते हैं । आप aggregateएक कंस्ट्रक्टर दे सकते हैं जिसमें 2 पूर्णांक लगते हैं, और {}सिंटैक्स का उपयोग करते समय इस कंस्ट्रक्टर को बुलाया जाएगा । यह कहा जा रहा है कि यदि आप कंस्ट्रक्टर को कॉल करने का प्रयास कर रहे हैं , emplace_backतो यह बेहतर होगा, क्योंकि यह कंस्ट्रक्टर को इन-प्लेस कहता है। और इसलिए प्रतिलिपि के प्रकार की आवश्यकता नहीं है।
निकोल बोलस जू

11
यह मानक में एक दोष के रूप में देखा गया था, और इसे हल किया गया है। देखें cplusplus.github.io/LWG/lwg-active.html#2089
डेविड स्टोन

3
@DavidStone इसे हल किया गया था, यह अभी भी "सक्रिय" सूची में नहीं होगा ... नहीं? यह एक उत्कृष्ट मुद्दा बना हुआ है। " [2018-08-23 बाटविया इश्यूज़ प्रोसेसिंग] " का नवीनतम अद्यतन, कहता है कि " P0960 (वर्तमान में उड़ान में) को इसे हल करना चाहिए। " और मैं अभी भी कोड संकलित नहीं कर सकता है जो emplaceस्पष्ट रूप से बॉयलर निर्माता को लिखने के बिना समुच्चय का प्रयास करता है। । यह इस बिंदु पर भी स्पष्ट नहीं है कि क्या इसे एक दोष के रूप में माना जाएगा और इस प्रकार बैकपोर्टिंग के लिए पात्र है, या C ++ <20 के उपयोगकर्ता SoL बने रहेंगे या नहीं।
अंडरस्कोर_ड

80

पूर्व-सी ++ 11 संकलक के साथ पीछे की संगतता।


21
यह C ++ का अभिशाप लगता है। हमें प्रत्येक नई रिलीज़ के साथ बहुत सारी शांत सुविधाएँ मिलती हैं, लेकिन बहुत सी कंपनियाँ या तो अनुकूलता के लिए कुछ पुराने संस्करण का उपयोग करके अटक जाती हैं या कुछ सुविधाओं के उपयोग को हतोत्साहित (यदि अस्वीकार नहीं करती हैं) करती हैं।
डैन अल्बर्ट

6
@ मेहरदाद: जब आप महान हो सकते हैं तो पर्याप्त के लिए समझौता क्यों करें? मुझे यकीन है कि ब्लब में प्रोग्रामिंग नहीं होगी , भले ही यह पर्याप्त था। यह कहते हुए कि विशेष रूप से इस उदाहरण के लिए मामला नहीं है, लेकिन किसी ऐसे व्यक्ति के रूप में जो संगतता के लिए C89 में अपना अधिकांश समय प्रोग्रामिंग में बिताता है, यह निश्चित रूप से एक वास्तविक समस्या है।
डैन अल्बर्ट

3
मुझे नहीं लगता कि यह वास्तव में सवाल का जवाब है। मेरे लिए वह उपयोग-मामलों के लिए पूछ रहा है, जहां push_backबेहतर है।
मिस्टर बॉय

3
@ Mr.Boy: जब आप पूर्व-सी ++ 11 संकलक के साथ पिछड़े-संगत होना चाहते हैं तो यह बेहतर है। क्या मेरे जवाब में यह स्पष्ट नहीं था?
user541686

6
यह, जिस तरह से और अधिक ध्यान मेरी अपेक्षा से किया है हो गया है तो यह पढ़ आप सभी के लिए: emplace_backहै की एक "महान" संस्करण push_back। यह इसका संभावित खतरनाक संस्करण है। अन्य उत्तर पढ़ें।
user541686

68

Emplace_back के कुछ पुस्तकालय कार्यान्वयन दृश्य स्टूडियो 2012, 2013 और 2015 के साथ जहाज वाले संस्करण सहित C ++ मानक में निर्दिष्ट व्यवहार नहीं करते हैं।

ज्ञात संकलक कीड़ों को समायोजित करने के लिए, std::vector::push_back()यदि पैरामीटर पुनरावृत्तियों या अन्य वस्तुओं को कॉल के बाद अमान्य होगा , तो उपयोग करना पसंद करें।

std::vector<int> v;
v.emplace_back(123);
v.emplace_back(v[0]); // Produces incorrect results in some compilers

एक संकलक पर, v में 123 और 123 के बजाय मान 123 और 21 शामिल हैं। यह इस तथ्य के कारण है कि 2 के emplace_backपरिणाम एक आकार में परिणामित होते हैं जिस बिंदु पर v[0]अमान्य हो जाता है।

उपरोक्त के push_back()बजाय उपरोक्त कोड का एक कार्यशील कार्यान्वयन उपयोग करेगा emplace_back():

std::vector<int> v;
v.emplace_back(123);
v.push_back(v[0]);

नोट: एक सदिश ints का उपयोग प्रदर्शन उद्देश्यों के लिए है। मैंने इस मुद्दे को बहुत अधिक जटिल वर्ग के साथ खोजा जिसमें डायनामिक रूप से आवंटित सदस्य चर और emplace_back()हार्ड क्रैश के परिणामस्वरूप कॉल शामिल था।


रुको। यह एक बग प्रतीत होता है। push_backइस मामले में अलग कैसे हो सकता है?
बाल्की

12
Emplace_back () के लिए कॉल जगह में निर्माण करने के लिए एकदम सही अग्रेषण का उपयोग करता है और जैसे v [0] का मूल्यांकन तब तक नहीं किया जाता है जब तक कि वेक्टर को आकार नहीं दिया जाता है (जिस बिंदु v [0] अमान्य है)। push_back नए तत्व का निर्माण करता है और कॉपी / तत्व को आवश्यकतानुसार स्थानांतरित करता है और v [0] का मूल्यांकन किसी भी वास्तविक से पहले किया जाता है।
मार्क

1
@Marc: यह उस मानक द्वारा गारंटीकृत है जो रेंज के अंदर तत्वों के लिए भी emplace_back काम करता है।
डेविड स्टोन

1
@ डेविडस्टोन: कृपया इस संदर्भ में इस व्यवहार की गारंटी देने के लिए एक संदर्भ प्रदान कर सकते हैं? किसी भी तरह से, विजुअल स्टूडियो 2012 और 2015 गलत व्यवहार प्रदर्शित करते हैं।
मार्क

4
@ कैमिनो: अनावश्यक कॉपी को कम करने के लिए इसके पैरामीटर के मूल्यांकन में देरी के लिए emplace_back मौजूद है। व्यवहार या तो अपरिभाषित है या एक कंपाइलर बग (मानक का लंबित विश्लेषण)। मैंने हाल ही में विजुअल स्टूडियो 2015 के खिलाफ एक ही टेस्ट चलाया और रिलीज़ x64 के तहत 123,3, रिलीज़ विन 32 के तहत 123,40 और डीबग एक्स 64 और डीबग विन 32 के तहत 123 -572662307 जारी किए।
मार्क
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.