निम्नलिखित कोड आपको "बड़े चित्र विचार" को समझने में मदद कर सकता है कि 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
नोटिस जो:
एक unordered_map
हमेशा आंतरिक रूप से Foo
वस्तुओं को संग्रहीत करता है (और कहते हैं, Foo *
एस) कुंजी के रूप में, जो नष्ट होने पर सभी नष्ट unordered_map
हो जाते हैं। यहाँ, unordered_map
13, 11, 5, 10, 7, और 9 के लिए आंतरिक कुंजियाँ हैं।
- तो तकनीकी रूप से, हमारी
unordered_map
वास्तव में std::pair<const Foo, int>
वस्तुओं को संग्रहीत करता है, जो बदले में Foo
वस्तुओं को संग्रहीत करता है। लेकिन "बड़े चित्र विचार" को समझने के लिए कैसे (नीचे हाइलाइट किए गए बॉक्स देखें) emplace()
से अलग है insert()
, यह पूरी तरह से निष्क्रिय होने के रूप में अस्थायी रूप से इस std::pair
ऑब्जेक्ट की कल्पना करना ठीक है । एक बार जब आप इस "बड़े चित्र विचार" को समझ लेते हैं, तो यह महत्वपूर्ण है कि आप यह समझ लें कि सूक्ष्म, लेकिन महत्वपूर्ण, तकनीकीताओं का परिचय std::pair
देकर इस मध्यस्थ वस्तु का उपयोग कैसे किया जाता है unordered_map
।
से प्रत्येक सम्मिलित करना foo0
, foo1
और foo2
से एक के लिए 2 कॉल की आवश्यकता Foo
की कॉपी / कदम निर्माणकर्ता और करने के लिए 2 कॉल Foo
के नाशक (के रूप में मैं अब का वर्णन):
ए। प्रत्येक को सम्मिलित करते हुए foo0
और foo1
एक अस्थायी ऑब्जेक्ट बनाया ( foo4
और foo6
, क्रमशः) जिसका विध्वंसक तब सम्मिलन पूरा होने के तुरंत बाद कहा जाता था। इसके अलावा, unordered_map के आंतरिक Foo
s (जो कि Foo
5 और 7 हैं) में उनके विध्वंसक भी थे जिन्हें unordered_map नष्ट होने पर बुलाया गया था।
ख। डालने के लिए foo2
, हमने पहले स्पष्ट रूप से एक गैर-अस्थायी जोड़ी वस्तु (कहा जाता है pair
) बनाई , जिसे Foo
'पर कॉपी कंस्ट्रक्टर' foo2
( foo8
आंतरिक सदस्य के रूप में बनाना pair
) कहा जाता है। फिर हम insert()
इस जोड़ी को एड करते हैं, जिसके परिणामस्वरूप unordered_map
कॉपी कंस्ट्रक्टर को फिर से (ऑन foo8
) अपनी आंतरिक कॉपी ( foo9
) बनाने के लिए कहा जाता है। जैसा कि foo
0 और 1 के साथ है, अंतिम परिणाम इस प्रविष्टि के लिए दो विध्वंसक कॉल थे जिनमें एकमात्र अंतर यह था कि foo8
विध्वंसक तभी कहा जाता था जब हम समाप्त main()
होने के तुरंत बाद बुलाए जाने के बजाय अंतिम छोर पर पहुंच जाते थे insert()
।
रिस्पांसिंग के foo3
परिणामस्वरूप केवल 1 कॉपी / मूव कंस्ट्रक्टर कॉल ( foo10
आंतरिक रूप से निर्माण करना unordered_map
) और केवल 1 कॉल Foo
डिस्ट्रक्टर्स के लिए होता है। (मैं बाद में इस पर वापस आऊँगा)।
इसके लिए foo11
, हमने सीधे पूर्णांक 11 को पारित कर दिया, emplace(11, d)
ताकि निर्माणकर्ता unordered_map
को कॉल किया जाए Foo(int)
जबकि निष्पादन इसकी emplace()
विधि के भीतर है । (2) और (3) के विपरीत, हमें ऐसा करने के लिए किसी पूर्व-पूर्व foo
वस्तु की आवश्यकता नहीं थी । महत्वपूर्ण रूप से, ध्यान दें कि एक Foo
निर्माणकर्ता को केवल 1 कॉल हुआ (जो बनाया गया foo11
)।
हमने फिर पूर्णांक 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)।
- जारी: कारण,
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
) के बजाय एक ऑब्जेक्ट (सिर्फ एक बदला हुआ नाम ) का उपयोग करेंint
unordered_map
unordered_map<Foo, Goo>
बजाय काunordered_map<Foo, int>
) के रूप में और देखें कि कितने और कौन से Goo
निर्माता कहा जाता है। (स्पॉयलर: एक प्रभाव है लेकिन यह बहुत नाटकीय नहीं है।)