सम्मिलित करें बनाम emplace संचालक [] c ++ मानचित्र में


189

मैं पहली बार नक्शे का उपयोग कर रहा हूं और मुझे महसूस हुआ कि किसी तत्व को सम्मिलित करने के कई तरीके हैं। आप का उपयोग कर सकते हैं emplace(), operator[]या insert(), प्लस वेरिएंट का उपयोग कर value_typeया जैसे make_pair। हालांकि उनमें से सभी के बारे में बहुत सारी जानकारी है और विशेष मामलों के बारे में प्रश्न हैं, फिर भी मैं बड़ी तस्वीर को नहीं समझ सकता। तो, मेरे दो प्रश्न हैं:

  1. उनमें से हर एक का दूसरों पर क्या फायदा है?

  2. क्या मानक में एमिल जोड़ने की कोई आवश्यकता थी? क्या ऐसा कुछ है जो इसके बिना संभव नहीं था?


1
विस्थापन शब्दार्थ स्पष्ट रूपांतरण और प्रत्यक्ष आरंभीकरण की अनुमति देता है।
केरेके एसबी

3
अब operator[]पर आधारित है try_emplace। यह ध्यान देने योग्य बात भी हो सकती है insert_or_assign
फ्रैंकएचबी

@FrankHB यदि आप (या कोई और) एक अप-टू-डेट उत्तर जोड़ता है, तो मैं स्वीकृत को बदल सकता हूं।
जर्मन कैपुआनो

जवाबों:


227

नक्शे के विशेष मामले में पुराने विकल्प केवल दो थे: operator[]और insert(विभिन्न स्वादों के insert)। तो मैं उन लोगों को समझाना शुरू कर दूंगा।

operator[]एक है खोजने या ऐड ऑपरेटर। यह नक्शे के अंदर दिए गए कुंजी के साथ एक तत्व खोजने की कोशिश करेगा, और अगर यह मौजूद है तो यह संग्रहीत मूल्य का एक संदर्भ लौटाएगा। यदि ऐसा नहीं होता है, तो यह डिफ़ॉल्ट इनिशियलाइज़ेशन के साथ डाला गया एक नया तत्व बनाएगा और इसका एक संदर्भ लौटाएगा।

insertसमारोह (एक तत्व स्वाद में) एक लेता है value_type( std::pair<const Key,Value>), यह कुंजी (का उपयोग करता firstसदस्य) और डालने की कोशिश करता है। क्योंकि std::mapडुप्लिकेट के लिए अनुमति नहीं है अगर कोई मौजूदा तत्व है तो यह कुछ भी नहीं डालेगा।

दोनों के बीच पहला अंतर यह है कि operator[]एक डिफ़ॉल्ट आरंभीकृत मूल्य का निर्माण करने में सक्षम होने की आवश्यकता है , और यह इस प्रकार मूल्य प्रकारों के लिए अनुपयोगी है जो डिफ़ॉल्ट आरंभीकृत नहीं किया जा सकता है। दोनों के बीच दूसरा अंतर तब होता है जब दिए गए कुंजी के साथ एक तत्व पहले से ही होता है। insertसमारोह नक्शे के राज्य में बदलाव नहीं करेगी, लेकिन इसके बजाय तत्व (और एक के लिए एक इटरेटर वापसी falseका संकेत है कि यह नहीं डाला गया था)।

// assume m is std::map<int,int> already has an element with key 5 and value 0
m[5] = 10;                      // postcondition: m[5] == 10
m.insert(std::make_pair(5,15)); // m[5] is still 10

insertतर्क के मामले में एक वस्तु है value_type, जिसे विभिन्न तरीकों से बनाया जा सकता है। आप इसे सीधे उपयुक्त प्रकार के साथ बना सकते हैं या किसी भी वस्तु को पास value_typeकर सकते हैं जिससे निर्माण किया जा सकता है, जो कि std::make_pairखेल में आता है, क्योंकि यह std::pairवस्तुओं के सरल निर्माण की अनुमति देता है , हालांकि यह संभवतः वह नहीं है जो आप चाहते हैं ...

निम्न कॉल का शुद्ध प्रभाव समान है :

K t; V u;
std::map<K,V> m;           // std::map<K,V>::value_type is std::pair<const K,V>

m.insert( std::pair<const K,V>(t,u) );      // 1
m.insert( std::map<K,V>::value_type(t,u) ); // 2
m.insert( std::make_pair(t,u) );            // 3

लेकिन वास्तव में समान नहीं हैं ... [1] और [२] वास्तव में बराबर हैं। दोनों मामलों में कोड एक ही प्रकार ( std::pair<const K,V>) का एक अस्थायी ऑब्जेक्ट बनाता है और इसे insertफ़ंक्शन में पास करता है। insertसमारोह द्विआधारी खोज वृक्ष में उचित नोड पैदा करेगा और उसके बाद की प्रतिलिपि value_typeनोड के लिए तर्क से भाग। उपयोग करने value_typeका लाभ यह है कि, value_typeहमेशा मेल खाता है value_type , आप std::pairतर्कों के प्रकार को गलत नहीं कर सकते हैं !

अंतर [3] में है। फ़ंक्शन std::make_pairएक टेम्पलेट फ़ंक्शन है जो ए बनाएगा std::pair। हस्ताक्षर है:

template <typename T, typename U>
std::pair<T,U> make_pair(T const & t, U const & u );

मैंने जानबूझकर टेम्पलेट तर्कों को प्रदान नहीं किया है std::make_pair, क्योंकि यह सामान्य उपयोग है। और निहितार्थ यह है कि इस मामले में, कॉल से तर्कों को घटाया जाता है T==K,U==V, इसलिए कॉल को std::make_pairवापस करने के लिए std::pair<K,V>(गायब होने पर ध्यान दें const)। हस्ताक्षर की आवश्यकता value_typeहै जो करीब है लेकिन कॉल से लौटे मूल्य के समान नहीं है std::make_pair। क्योंकि यह काफी करीब है, यह सही प्रकार का एक अस्थायी निर्माण करेगा और इसे प्रारंभिक रूप से कॉपी करेगा। यह बदले में नोड की प्रतिलिपि बनाई जाएगी, कुल दो प्रतियां बनाएंगे।

यह टेम्पलेट तर्क प्रदान करके तय किया जा सकता है:

m.insert( std::make_pair<const K,V>(t,u) );  // 4

लेकिन यह अभी भी उसी तरह त्रुटि है, जो स्पष्ट रूप से टाइप करने के मामले में टाइप करता है [1]।

इस बिंदु तक, हमारे पास कॉल करने के अलग-अलग तरीके हैं जो कंटेनर में बाहरी रूप से और उस ऑब्जेक्ट की प्रतिलिपि insertबनाने की आवश्यकता है value_type। वैकल्पिक रूप से आप उपयोग कर सकते हैं operator[]यदि प्रकार डिफ़ॉल्ट रूप से रचनात्मक और असाइन करने योग्य है (जानबूझकर केवल ध्यान केंद्रित कर रहा है m[k]=v), और इसके लिए एक वस्तु के डिफ़ॉल्ट आरंभीकरण और उस वस्तु में मूल्य की प्रतिलिपि की आवश्यकता होती है ।

C ++ 11 में, वैरेडिक टेम्प्लेट्स और परफेक्ट फॉरवर्डिंग के साथ एक कंटेनर में तत्वों को जोड़ने का एक नया तरीका है, अनुकरण करके (जगह बनाना)। emplaceविभिन्न कंटेनरों में कार्यों मूल रूप से एक ही बात करते हैं: एक हो रही के बजाय स्रोत है जहाँ से आप को कॉपी कंटेनर में, फ़ंक्शन पैरामीटर है कि वस्तु कंटेनर में संग्रहित की निर्माता को भेजा जाएगा लेता है।

m.emplace(t,u);               // 5

[५] में, std::pair<const K, V>बनाया और पास नहीं किया जाता emplace, बल्कि डेटा संरचना के अंदर सब -जेक्ट के कंस्ट्रक्टर के पास tऔर uऑब्जेक्ट को संदर्भित किया जाता emplaceहै value_type। इस मामले में किसी भी तरह की कोई भी कॉपी नहीं की std::pair<const K,V>जाती है, जो emplaceकि C ++ 03 विकल्प से अधिक का लाभ है । जैसा कि इसके मामले insertमें नक्शे में मूल्य को ओवरराइड नहीं किया जाएगा।


एक दिलचस्प सवाल जो मैंने नहीं सोचा था कि emplaceवास्तव में एक नक्शे के लिए कैसे लागू किया जा सकता है, और यह सामान्य मामले में एक साधारण समस्या नहीं है।


5
यह उत्तर में संकेत दिया गया है, लेकिन मानचित्र [] = वैल्यू पिछले मान को अधिलेखित कर देगा यदि कोई मौजूद है।
dk123

मेरे अर्थ में एक और दिलचस्प सवाल यह है कि यह बहुत कम उद्देश्य से कार्य करता है। क्योंकि आप जोड़ी कॉपी को बचाते हैं, जो कि अच्छा है क्योंकि कोई भी जोड़ी कॉपी का मतलब कोई mapped_typeआईसेंटेंस कॉपी नहीं है। हम जो चाहते हैं, mapped_typeजोड़ी में निर्माण का अनुकरण करते हैं, और नक्शे में जोड़ी के निर्माण का अनुकरण करते हैं। इसलिए, std::pair::emplaceफ़ंक्शन और इसके अग्रेषण समर्थन map::emplaceदोनों गायब हैं। इसके वर्तमान स्वरूप में, आपको अभी भी जोड़े वाले कंस्ट्रक्टर को एक निर्मित मैप्ड_टाइप देना होगा, जो इसे एक बार कॉपी करेगा। दो बार से बेहतर है, लेकिन अभी भी अच्छा नहीं है।
v.oddou

वास्तव में मैं उस टिप्पणी में संशोधन करता हूं, सी ++ 11 में एक टेम्पलेट पेयर कंस्ट्रक्टर है जो 1 तर्क निर्माण के मामले में एमप्लेस की तुलना में सटीक उद्देश्य प्रदान करता है। और कुछ अजीब टुकड़े-टुकड़े का निर्माण, जैसा कि वे इसे कहते हैं, आगे की दलीलों के लिए ट्यूपल्स का उपयोग कर रहे हैं, इसलिए हम अभी भी सही अग्रेषित कर सकते हैं ऐसा लगता है।
v.oddou

ऐसा लगता है कि वहाँ unordered_map और नक्शे में डालने का एक प्रदर्शन बग है: लिंक
Deqing

1
इस जानकारी को insert_or_assignऔर try_emplace(C ++ 17 से दोनों) के साथ अद्यतन करना अच्छा हो सकता है , जो मौजूदा तरीकों से कार्यक्षमता में कुछ अंतराल भरने में मदद करता है।
शैडो रेंजर

14

Emplace: आपके द्वारा पहले ही बनाई गई वास्तविक वस्तुओं का उपयोग करने के लिए प्रतिद्वंद्विता संदर्भ का लाभ उठाता है। इसका मतलब यह है कि कोई भी कॉपी या मूव कंस्ट्रक्टर को नहीं कहा जाता है, लार्स ऑब्जेक्ट्स के लिए अच्छा है! ओ (लॉग (एन)) समय।

सम्मिलित करें: मानक लवल्यू संदर्भ और व्याप्त संदर्भ के लिए अधिभार है, साथ ही सम्मिलित करने के लिए तत्वों की सूचियों के लिए पुनरावृत्तियों, और तत्व के अनुसार "संकेत" है। एक "संकेत" पुनरावृत्ति का उपयोग समय डालने का समय आकस्मिक समय तक ले सकता है, अन्यथा यह ओ (लॉग (एन)) समय है।

संचालक []: यह देखने के लिए जाँचता है कि क्या वस्तु मौजूद है, और यदि वह करता है, तो इस ऑब्जेक्ट के संदर्भ को संशोधित करता है, अन्यथा दो वस्तुओं पर make_pair को कॉल करने के लिए प्रदान की गई कुंजी और मूल्य का उपयोग करता है, और फिर सम्मिलित फ़ंक्शन के समान कार्य करता है। यह O (लॉग (N)) समय है।

make_pair: एक जोड़ी बनाने की तुलना में थोड़ा अधिक है।

मानक में एम्प्ले जोड़ने के लिए कोई "आवश्यकता" नहीं थी। C ++ 11 में मेरा मानना ​​है कि && प्रकार का संदर्भ जोड़ा गया था। इसने चाल शब्दार्थों के लिए आवश्यकता को हटा दिया, और कुछ विशिष्ट प्रकार के स्मृति प्रबंधन के अनुकूलन की अनुमति दी। विशेष रूप से, प्रतिद्वंद्विता संदर्भ। ओवरलोड डाला (value_type &&) ऑपरेटर in_place शब्दार्थ का लाभ नहीं उठाता है, और इसलिए यह बहुत कम कुशल है। हालांकि यह प्रतिद्वंद्वियों के संदर्भों से निपटने की क्षमता प्रदान करता है, यह उनके प्रमुख उद्देश्य की अनदेखी करता है, जो वस्तुओं के निर्माण में होता है।


4
" मानक में एम्प्ले जोड़ने के लिए कोई" आवश्यकता "नहीं थी।" यह गलत है। emplace()केवल एक तत्व को सम्मिलित करने का एकमात्र तरीका है जिसे कॉपी या स्थानांतरित नहीं किया जा सकता है। (और हाँ, शायद, सबसे करने के लिए कुशलतापूर्वक जिसका डालने कॉपी और चाल कंस्ट्रक्टर्स एक बहुत निर्माण की तुलना में अधिक लागत अगर ऐसी बात मौजूद है) यह भी लगता है कि आप विचार गलत: यह नहीं के बारे में "है [लेने] rvalue संदर्भ का लाभ उन वास्तविक वस्तुओं का उपयोग करने के लिए जिन्हें आपने पहले ही बनाया है ”; कोई वस्तु अभी तक बनाया जाता है, और आप आगे mapतर्क यह अंदर ही इसे बनाने की जरूरत है। आप वस्तु नहीं बनाते हैं।
अंडरस्कोर_ड

10

अनुकूलन के अवसरों और सरल सिंटैक्स के अलावा, सम्मिलन और विस्थापन के बीच एक महत्वपूर्ण अंतर यह है कि उत्तरार्द्ध स्पष्ट रूपांतरण की अनुमति देता है । (यह केवल मानचित्रों के लिए नहीं, बल्कि पूरे मानक पुस्तकालय में है।)

यहाँ एक उदाहरण प्रदर्शित करना है:

#include <vector>

struct foo
{
    explicit foo(int);
};

int main()
{
    std::vector<foo> v;

    v.emplace(v.end(), 10);      // Works
    //v.insert(v.end(), 10);     // Error, not explicit
    v.insert(v.end(), foo(10));  // Also works
}

यह वास्तव में एक बहुत ही विशिष्ट विवरण है, लेकिन जब आप उपयोगकर्ता-परिभाषित रूपांतरणों की श्रृंखलाओं के साथ काम कर रहे होते हैं, तो यह ध्यान में रखना आवश्यक है।


कल्पना कीजिए कि फू को इसके ctor में एक के बजाय दो ints की आवश्यकता थी। क्या आप इस कॉल का उपयोग कर पाएंगे? v.emplace(v.end(), 10, 10); ... या अब आपको उपयोग करने की आवश्यकता होगी v.emplace(v.end(), foo(10, 10) ); :?
काइटेन

मेरे पास अभी एक संकलक तक पहुंच नहीं है, लेकिन मैं मान लूंगा कि इसका मतलब है कि दोनों संस्करण काम करेंगे। लगभग सभी उदाहरण आप emplaceएक एकल पैरामीटर लेने वाले वर्ग के उपयोग के लिए देखते हैं । IMO यह वास्तव में एमप्ले के वैरिएड सिंटैक्स की प्रकृति को और अधिक स्पष्ट कर देगा यदि कई मापदंडों का उपयोग उदाहरणों में किया गया हो।
काइटैन

9

निम्नलिखित कोड आपको "बड़े चित्र विचार" को समझने में मदद कर सकता है कि insert()इससे कैसे भिन्न होता है emplace():

#include <iostream>
#include <unordered_map>
#include <utility>

//Foo simply outputs what constructor is called with what value.
struct Foo {
  static int foo_counter; //Track how many Foo objects have been created.
  int val; //This Foo object was the val-th Foo object to be created.

  Foo() { val = foo_counter++;
    std::cout << "Foo() with val:                " << val << '\n';
  }
  Foo(int value) : val(value) { foo_counter++;
    std::cout << "Foo(int) with val:             " << val << '\n';
  }
  Foo(Foo& f2) { val = foo_counter++;
    std::cout << "Foo(Foo &) with val:           " << val
              << " \tcreated from:      \t" << f2.val << '\n';
  }
  Foo(const Foo& f2) { val = foo_counter++;
    std::cout << "Foo(const Foo &) with val:     " << val
              << " \tcreated from:      \t" << f2.val << '\n';
  }
  Foo(Foo&& f2) { val = foo_counter++;
    std::cout << "Foo(Foo&&) moving:             " << f2.val
              << " \tand changing it to:\t" << val << '\n';
  }
  ~Foo() { std::cout << "~Foo() destroying:             " << val << '\n'; }

  Foo& operator=(const Foo& rhs) {
    std::cout << "Foo& operator=(const Foo& rhs) with rhs.val: " << rhs.val
              << " \tcalled with lhs.val = \t" << val
              << " \tChanging lhs.val to: \t" << rhs.val << '\n';
    val = rhs.val;
    return *this;
  }

  bool operator==(const Foo &rhs) const { return val == rhs.val; }
  bool operator<(const Foo &rhs)  const { return val < rhs.val;  }
};

int Foo::foo_counter = 0;

//Create a hash function for Foo in order to use Foo with unordered_map
namespace std {
   template<> struct hash<Foo> {
       std::size_t operator()(const Foo &f) const {
           return std::hash<int>{}(f.val);
       }
   };
}

int main()
{
    std::unordered_map<Foo, int> umap;  
    Foo foo0, foo1, foo2, foo3;
    int d;

    //Print the statement to be executed and then execute it.

    std::cout << "\numap.insert(std::pair<Foo, int>(foo0, d))\n";
    umap.insert(std::pair<Foo, int>(foo0, d));
    //Side note: equiv. to: umap.insert(std::make_pair(foo0, d));

    std::cout << "\numap.insert(std::move(std::pair<Foo, int>(foo1, d)))\n";
    umap.insert(std::move(std::pair<Foo, int>(foo1, d)));
    //Side note: equiv. to: umap.insert(std::make_pair(foo1, d));

    std::cout << "\nstd::pair<Foo, int> pair(foo2, d)\n";
    std::pair<Foo, int> pair(foo2, d);

    std::cout << "\numap.insert(pair)\n";
    umap.insert(pair);

    std::cout << "\numap.emplace(foo3, d)\n";
    umap.emplace(foo3, d);

    std::cout << "\numap.emplace(11, d)\n";
    umap.emplace(11, d);

    std::cout << "\numap.insert({12, d})\n";
    umap.insert({12, d});

    std::cout.flush();
}

मुझे जो आउटपुट मिला वह था:

Foo() with val:                0
Foo() with val:                1
Foo() with val:                2
Foo() with val:                3

umap.insert(std::pair<Foo, int>(foo0, d))
Foo(Foo &) with val:           4    created from:       0
Foo(Foo&&) moving:             4    and changing it to: 5
~Foo() destroying:             4

umap.insert(std::move(std::pair<Foo, int>(foo1, d)))
Foo(Foo &) with val:           6    created from:       1
Foo(Foo&&) moving:             6    and changing it to: 7
~Foo() destroying:             6

std::pair<Foo, int> pair(foo2, d)
Foo(Foo &) with val:           8    created from:       2

umap.insert(pair)
Foo(const Foo &) with val:     9    created from:       8

umap.emplace(foo3, d)
Foo(Foo &) with val:           10   created from:       3

umap.emplace(11, d)
Foo(int) with val:             11

umap.insert({12, d})
Foo(int) with val:             12
Foo(const Foo &) with val:     13   created from:       12
~Foo() destroying:             12

~Foo() destroying:             8
~Foo() destroying:             3
~Foo() destroying:             2
~Foo() destroying:             1
~Foo() destroying:             0
~Foo() destroying:             13
~Foo() destroying:             11
~Foo() destroying:             5
~Foo() destroying:             10
~Foo() destroying:             7
~Foo() destroying:             9

नोटिस जो:

  1. एक unordered_mapहमेशा आंतरिक रूप से Fooवस्तुओं को संग्रहीत करता है (और कहते हैं, Foo *एस) कुंजी के रूप में, जो नष्ट होने पर सभी नष्ट unordered_mapहो जाते हैं। यहाँ, unordered_map13, 11, 5, 10, 7, और 9 के लिए आंतरिक कुंजियाँ हैं।

    • तो तकनीकी रूप से, हमारी unordered_mapवास्तव में std::pair<const Foo, int>वस्तुओं को संग्रहीत करता है, जो बदले में Fooवस्तुओं को संग्रहीत करता है। लेकिन "बड़े चित्र विचार" को समझने के लिए कैसे (नीचे हाइलाइट किए गए बॉक्स देखें) emplace()से अलग है insert(), यह पूरी तरह से निष्क्रिय होने के रूप में अस्थायी रूप से इस std::pairऑब्जेक्ट की कल्पना करना ठीक है । एक बार जब आप इस "बड़े चित्र विचार" को समझ लेते हैं, तो यह महत्वपूर्ण है कि आप यह समझ लें कि सूक्ष्म, लेकिन महत्वपूर्ण, तकनीकीताओं का परिचय std::pairदेकर इस मध्यस्थ वस्तु का उपयोग कैसे किया जाता है unordered_map
  2. से प्रत्येक सम्मिलित करना foo0, foo1और foo2से एक के लिए 2 कॉल की आवश्यकता Fooकी कॉपी / कदम निर्माणकर्ता और करने के लिए 2 कॉल Fooके नाशक (के रूप में मैं अब का वर्णन):

    ए। प्रत्येक को सम्मिलित करते हुए foo0और foo1एक अस्थायी ऑब्जेक्ट बनाया ( foo4और foo6, क्रमशः) जिसका विध्वंसक तब सम्मिलन पूरा होने के तुरंत बाद कहा जाता था। इसके अलावा, unordered_map के आंतरिक Foos (जो कि Foo5 और 7 हैं) में उनके विध्वंसक भी थे जिन्हें unordered_map नष्ट होने पर बुलाया गया था।

    ख। डालने के लिए foo2, हमने पहले स्पष्ट रूप से एक गैर-अस्थायी जोड़ी वस्तु (कहा जाता है pair) बनाई , जिसे Foo'पर कॉपी कंस्ट्रक्टर' foo2( foo8आंतरिक सदस्य के रूप में बनाना pair) कहा जाता है। फिर हम insert()इस जोड़ी को एड करते हैं, जिसके परिणामस्वरूप unordered_mapकॉपी कंस्ट्रक्टर को फिर से (ऑन foo8) अपनी आंतरिक कॉपी ( foo9) बनाने के लिए कहा जाता है। जैसा कि foo0 और 1 के साथ है, अंतिम परिणाम इस प्रविष्टि के लिए दो विध्वंसक कॉल थे जिनमें एकमात्र अंतर यह था कि foo8विध्वंसक तभी कहा जाता था जब हम समाप्त main()होने के तुरंत बाद बुलाए जाने के बजाय अंतिम छोर पर पहुंच जाते थे insert()

  3. रिस्पांसिंग के foo3परिणामस्वरूप केवल 1 कॉपी / मूव कंस्ट्रक्टर कॉल ( foo10आंतरिक रूप से निर्माण करना unordered_map) और केवल 1 कॉल Fooडिस्ट्रक्टर्स के लिए होता है। (मैं बाद में इस पर वापस आऊँगा)।

  4. इसके लिए foo11, हमने सीधे पूर्णांक 11 को पारित कर दिया, emplace(11, d)ताकि निर्माणकर्ता unordered_mapको कॉल किया जाए Foo(int)जबकि निष्पादन इसकी emplace()विधि के भीतर है । (2) और (3) के विपरीत, हमें ऐसा करने के लिए किसी पूर्व-पूर्व fooवस्तु की आवश्यकता नहीं थी । महत्वपूर्ण रूप से, ध्यान दें कि एक Fooनिर्माणकर्ता को केवल 1 कॉल हुआ (जो बनाया गया foo11)।

  5. हमने फिर पूर्णांक 12 को सीधे पास कर दिया insert({12, d})। इसके विपरीत emplace(11, d)(जिसके कारण एक Fooकंस्ट्रक्टर के लिए केवल 1 कॉल का परिणाम होता है ), इस कॉल के insert({12, d})परिणामस्वरूप दो कॉल्स Fooकंस्ट्रक्टर (बनाने foo12और बनाने foo13) में होते हैं।

इससे पता चलता है कि मुख्य "बड़ी तस्वीर" में क्या अंतर है insert()और emplace():

जबकि का उपयोग करते हुए insert() लगभग हमेशा निर्माण या कुछ के अस्तित्व की आवश्यकता है Fooमें वस्तु main()की गुंजाइश (एक प्रतिलिपि या इस कदम के बाद) का उपयोग किया है, तो emplace()फिर एक के लिए किसी भी कॉल Fooनिर्माता में पूरी तरह से आंतरिक रूप से किया जाता है unordered_map(के दायरे के अंदर यानी emplace()विधि की परिभाषा)। आपके द्वारा पास की जाने वाली कुंजी के लिए तर्क (ओं) emplace()को सीधे एक Fooकॉन्ट्रैक्टर कॉल पर भेजा जाता है unordered_map::emplace()(वैकल्पिक अतिरिक्त विवरण: जहां इस नवनिर्मित वस्तु को तुरंत एक unordered_mapसदस्यीय चर में शामिल किया जाता है ताकि कोई विध्वंसक न हो निष्पादन के पत्ते emplace()और कोई भी चाल या कॉपी कंस्ट्रक्टर नहीं कहा जाता है)।

नोट: " लगभग हमेशा " ऊपर " लगभग " का कारण नीचे दिया गया है I)।

  1. जारी: कारण, umap.emplace(foo3, d)जिसे Fooगैर-कॉन्स्टल कॉपी कंस्ट्रक्टर कहा जाता है, निम्नलिखित है: चूंकि हम उपयोग कर रहे हैं emplace(), कंपाइलर जानता है कि foo3(एक नॉन-कॉस्ट Fooऑब्जेक्ट) कुछ Fooकंस्ट्रक्टर के लिए एक तर्क है । इस मामले में, सबसे फिटिंग Fooकंस्ट्रक्टर गैर-कॉन्स्टल कॉपी कंस्ट्रक्टर है Foo(Foo& f2)। यही कारण है कि umap.emplace(foo3, d)कॉपी कंस्ट्रक्टर कहा जाता है जबकि umap.emplace(11, d)नहीं।

उपसंहार:

I. ध्यान दें कि एक अधिभार insert()वास्तव में के बराबर है emplace() । जैसा कि इस cppreference.com पृष्ठ पर वर्णित है , अधिभार template<class P> std::pair<iterator, bool> insert(P&& value)(जो insert()इस cppreference.com पृष्ठ पर अधिभार (2) है ) के बराबर है emplace(std::forward<P>(value))

द्वितीय। यहाँ से कहाँ जाएं?

ए। उपरोक्त स्रोत कोड के साथ खेलें और ऑनलाइन insert()(जैसे यहां ) और emplace()(जैसे यहां ) के लिए प्रलेखन का अध्ययन करें । यदि आप एक IDE जैसे कि ग्रहण या NetBeans का उपयोग कर रहे हैं, तो आप आसानी से अपना IDE प्राप्त कर सकते हैं, जिससे आपको पता चल सके कि कौन सा अधिभार है insert()या emplace()कहा जा रहा है (ग्रहण में, बस अपने माउस के कर्सर को फ़ंक्शन कॉल पर एक सेकंड के लिए स्थिर रखें)। यहां कुछ और कोड आज़माए गए हैं:

std::cout << "\numap.insert({{" << Foo::foo_counter << ", d}})\n";
umap.insert({{Foo::foo_counter, d}});
//but umap.emplace({{Foo::foo_counter, d}}); results in a compile error!

std::cout << "\numap.insert(std::pair<const Foo, int>({" << Foo::foo_counter << ", d}))\n";
umap.insert(std::pair<const Foo, int>({Foo::foo_counter, d}));
//The above uses Foo(int) and then Foo(const Foo &), as expected. but the
// below call uses Foo(int) and the move constructor Foo(Foo&&). 
//Do you see why?
std::cout << "\numap.insert(std::pair<Foo, int>({" << Foo::foo_counter << ", d}))\n";
umap.insert(std::pair<Foo, int>({Foo::foo_counter, d}));
//Not only that, but even more interesting is how the call below uses all 
// three of Foo(int) and the Foo(Foo&&) move and Foo(const Foo &) copy 
// constructors, despite the below call's only difference from the call above 
// being the additional { }.
std::cout << "\numap.insert({std::pair<Foo, int>({" << Foo::foo_counter << ", d})})\n";
umap.insert({std::pair<Foo, int>({Foo::foo_counter, d})});


//Pay close attention to the subtle difference in the effects of the next 
// two calls.
int cur_foo_counter = Foo::foo_counter;
std::cout << "\numap.insert({{cur_foo_counter, d}, {cur_foo_counter+1, d}}) where " 
  << "cur_foo_counter = " << cur_foo_counter << "\n";
umap.insert({{cur_foo_counter, d}, {cur_foo_counter+1, d}});

std::cout << "\numap.insert({{Foo::foo_counter, d}, {Foo::foo_counter+1, d}}) where "
  << "Foo::foo_counter = " << Foo::foo_counter << "\n";
umap.insert({{Foo::foo_counter, d}, {Foo::foo_counter+1, d}});


//umap.insert(std::initializer_list<std::pair<Foo, int>>({{Foo::foo_counter, d}}));
//The call below works fine, but the commented out line above gives a 
// compiler error. It's instructive to find out why. The two calls
// differ by a "const".
std::cout << "\numap.insert(std::initializer_list<std::pair<const Foo, int>>({{" << Foo::foo_counter << ", d}}))\n";
umap.insert(std::initializer_list<std::pair<const Foo, int>>({{Foo::foo_counter, d}}));

आप जल्द ही देखेंगे कि std::pairनिर्माणकर्ता का कौन सा अधिभार ( संदर्भ देखें ) का उपयोग किया unordered_mapजा रहा है, इस पर एक महत्वपूर्ण प्रभाव हो सकता है कि कितनी वस्तुओं की नकल की जाती है, स्थानांतरित की जाती है, बनाई जाती है, और / या तब नष्ट हो जाती है जब यह सब होता है।

ख। देखें कि क्या होता है जब आप इसके बजाय कुछ अन्य कंटेनर वर्ग (जैसे std::setया std::unordered_multiset) का उपयोग करते हैं std::unordered_map

सी। अब श्रेणी के प्रकार (यानी उपयोग के रूप Gooमें Foo) के बजाय एक ऑब्जेक्ट (सिर्फ एक बदला हुआ नाम ) का उपयोग करेंintunordered_mapunordered_map<Foo, Goo> बजाय काunordered_map<Foo, int> ) के रूप में और देखें कि कितने और कौन से Gooनिर्माता कहा जाता है। (स्पॉयलर: एक प्रभाव है लेकिन यह बहुत नाटकीय नहीं है।)


0

कार्यक्षमता या आउटपुट के संदर्भ में, वे दोनों समान हैं।

दोनों बड़ी मेमोरी के लिए, ऑब्जेक्ट एमप्ले मेमोरी-ऑप्टिमाइज़्ड है जो कॉपी कंस्ट्रक्टर्स का उपयोग नहीं करते हैं

सरल विस्तृत विवरण के लिए https://medium.com/@sandywits/all-about-emplace-in-c-71fd15e06e44

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.