क्या C ++ मानक समिति द्वारा यह इरादा है कि C ++ 11 में unordered_map आवेषण को नष्ट कर देता है?


114

मैंने अपने जीवन के तीन दिन खो दिए हैं, एक बहुत ही अजीब बग को ट्रैक करना है जहाँ unordered_map :: सम्मिलित करना () आपके द्वारा डाले गए चर को नष्ट कर देता है। यह अत्यधिक गैर-स्पष्ट व्यवहार केवल हाल ही के संकलक में होता है: मैंने पाया कि 3.2-3.4 और जीसीसी 4.8 इस "सुविधा" को प्रदर्शित करने के लिए केवल संकलक हैं।

यहाँ मेरे मुख्य कोड आधार से कुछ कम कोड है जो समस्या को प्रदर्शित करता है:

#include <memory>
#include <unordered_map>
#include <iostream>

int main(void)
{
  std::unordered_map<int, std::shared_ptr<int>> map;
  auto a(std::make_pair(5, std::make_shared<int>(5)));
  std::cout << "a.second is " << a.second.get() << std::endl;
  map.insert(a); // Note we are NOT doing insert(std::move(a))
  std::cout << "a.second is now " << a.second.get() << std::endl;
  return 0;
}

मैं, शायद अधिकांश C ++ प्रोग्रामर की तरह, आउटपुट से कुछ इस तरह दिखने की उम्मीद करेंगे:

a.second is 0x8c14048
a.second is now 0x8c14048

लेकिन क्लैंग 3.2-3.4 और जीसीसी 4.8 के साथ मुझे इसके बजाय मिलता है:

a.second is 0xe03088
a.second is now 0

जिसका कोई मतलब नहीं है, जब तक कि आप unordered_map :: डालने () के लिए http://www.cplusplus.com/reference/unordered_map/unordered_map/insert/ पर करीब से जांच न करें, जहां ओवरलोड नंबर 2 है:

template <class P> pair<iterator,bool> insert ( P&& val );

जो एक लालची सार्वभौमिक संदर्भ चाल अधिभार है, जो किसी भी चीज को अन्य अधिभार से मेल नहीं खाता है, और इसका निर्माण एक value_type में ले जाता है। तो ऊपर दिए गए हमारे कोड ने इस अधिभार को क्यों चुना, और न कि unordered_map :: value_type अधिभार के रूप में शायद सबसे अधिक उम्मीद होगी?

उत्तर आपको चेहरे पर घूरता है: unordered_map :: value_type एक जोड़ी है < const int, std :: साझा_ptr > और कंपाइलर सही ढंग से सोचेंगे कि एक जोड़ी < int , std :: साझा_ptr> संभव नहीं है। इसलिए संकलक चाल सार्वभौमिक संदर्भ अधिभार को चुनता है, और जो प्रोग्रामर को std :: move () का उपयोग नहीं करने के बावजूद मूल को नष्ट कर देता है, जो यह संकेत देने के लिए विशिष्ट सम्मेलन है कि आप एक चर नष्ट होने के साथ ठीक हैं। इसलिए डालने को नष्ट करने वाला व्यवहार वास्तव में C ++ 11 मानक के अनुसार सही है , और पुराने संकलक गलत थे ।

आप शायद देख सकते हैं कि इस बग के निदान में मुझे तीन दिन क्यों लगे। यह एक बड़े कोड बेस में बिल्कुल स्पष्ट नहीं था, जहां टाइप किया जा रहा है unordered_map में टाइप किए गए स्रोत कोड की शर्तों में बहुत दूर परिभाषित किया गया था, और यह जांचने के लिए किसी के साथ कभी नहीं हुआ कि क्या टाइपडिफ value_type के समान था।

इसलिए मेरे सवालों को ढेर करने के लिए:

  1. पुराने संकलक नए संकलक की तरह डाले गए चरों को नष्ट नहीं करते हैं? मेरा मतलब है, यहां तक ​​कि GCC 4.7 ऐसा नहीं करता है, और यह सुंदर मानकों के अनुरूप है।

  2. क्या इस समस्या को व्यापक रूप से जाना जाता है, क्योंकि निश्चित रूप से अपग्रेडिंग कम्पाइलर कोड का कारण बनेंगे जो अचानक काम करना बंद कर देते थे?

  3. क्या C ++ मानक समिति ने इस व्यवहार का इरादा किया था?

  4. आप यह कैसे सुझाएंगे कि unordered_map :: प्रविष्टि () को बेहतर व्यवहार देने के लिए संशोधित किया जाए? मैं यह पूछता हूं क्योंकि अगर यहां समर्थन है, तो मैं इस व्यवहार को डब्ल्यूजी 21 को एन नोट के रूप में प्रस्तुत करने का इरादा रखता हूं और उनसे बेहतर व्यवहार को लागू करने के लिए कहता हूं।


10
सिर्फ इसलिए कि यह एक सार्वभौमिक रेफ का उपयोग करता है इसका मतलब यह नहीं है कि डाला गया मूल्य हमेशा स्थानांतरित हो जाता है - यह केवल कभी-कभी प्रतिद्वंद्वियों के लिए करना चाहिए , जो कि सादा aनहीं है। इसकी एक प्रति बनानी चाहिए। इसके अलावा, यह व्यवहार पूरी तरह से stdlib पर निर्भर करता है, संकलक पर नहीं।
X3

10
यह पुस्तकालय के कार्यान्वयन में एक बग की तरह लगता है
डेविड रोड्रिगेज -

4
"इसलिए व्यवहार को नष्ट करने वाला इन्सर्ट वास्तव में C ++ 11 मानक के अनुसार सही है, और पुराने संकलक गलत थे।" क्षमा करें, लेकिन आप गलत हैं। C ++ मानक के किस भाग से आपको वह विचार प्राप्त हुआ? BTW cplusplus.com आधिकारिक नहीं है।
बेन Voigt

1
मैं अपने सिस्टम पर इसे पुन: पेश नहीं कर सकता, और मैं 4.9.0 20131223 (experimental)क्रमशः gcc 4.8.2 और क्रमशः उपयोग कर रहा हूं । a.second is now 0x2074088 मेरे लिए आउटपुट (या समान) है।

47
यह जीसीसी बग 57619 था , 4.8 श्रृंखला में एक प्रतिगमन जो 2013-06 में 4.8.2 के लिए तय किया गया था।
केसी

जवाबों:


83

जैसा कि दूसरों ने टिप्पणियों में बताया है, "सार्वभौमिक" निर्माता वास्तव में, हमेशा अपने तर्क से आगे बढ़ने के लिए नहीं है। यदि तर्क वास्तव में एक प्रतिद्वंद्विता है, तो इसे स्थानांतरित करना चाहिए, और यदि यह एक अंतराल है तो इसे कॉपी करें।

व्यवहार, आप निरीक्षण करते हैं, जो हमेशा चलता रहता है, libstdc ++ में एक बग है, जो अब प्रश्न पर एक टिप्पणी के अनुसार तय किया गया है। उन उत्सुक लोगों के लिए, मैंने g ++ - 4.8 हेडर पर एक नज़र डाली।

bits/stl_map.h, लाइनें 598-603

  template<typename _Pair, typename = typename
           std::enable_if<std::is_constructible<value_type,
                                                _Pair&&>::value>::type>
    std::pair<iterator, bool>
    insert(_Pair&& __x)
    { return _M_t._M_insert_unique(std::forward<_Pair>(__x)); }

bits/unordered_map.h, लाइनें 365-370

  template<typename _Pair, typename = typename
           std::enable_if<std::is_constructible<value_type,
                                                _Pair&&>::value>::type>
    std::pair<iterator, bool>
    insert(_Pair&& __x)
    { return _M_h.insert(std::move(__x)); }

उत्तरार्द्ध गलत तरीके से उपयोग कर रहा है std::moveजहां इसका उपयोग किया जाना चाहिए std::forward


11
क्लैग डिफॉल्टस्टेक ++ का डिफ़ॉल्ट रूप से उपयोग करता है, जीसीसी का स्टडीलिब।
XIO

मैं 4.9 का उपयोग कर रहा हूँ और मैं देख रहा हूँ libstdc++-v3/include/bits/। मैं एक ही चीज नहीं देखता। मैं देखता हूं { return _M_h.insert(std::forward<_Pair>(__x)); }। यह 4.8 के लिए अलग हो सकता है, लेकिन मैंने अभी तक जांच नहीं की है।

हाँ, तो मुझे लगता है कि उन्होंने बग को ठीक कर दिया है।
ब्रायन

@ ब्रायन नोप, मैंने अभी-अभी अपने सिस्टम हेडर चेक किए हैं। वही चीज। 4.8.2 btw।

मेरा 4.8.1, इसलिए मुझे लगता है कि यह दोनों के बीच तय किया गया था।
ब्रायन

20
template <class P> pair<iterator,bool> insert ( P&& val );

जो एक लालची सार्वभौमिक संदर्भ चाल अधिभार है, जो किसी भी चीज को अन्य अधिभार से मेल नहीं खाता है, और इसका निर्माण एक value_type में ले जाता है।

यही कुछ लोग सार्वभौमिक संदर्भ कहते हैं , लेकिन वास्तव में संदर्भ पतन है । आपके मामले में, जहां तर्क एक प्रकार का अंतराल है , pair<int,shared_ptr<int>>यह तर्क के संदर्भ में तर्क का परिणाम नहीं होगा और इसे इससे नहीं हटना चाहिए

तो ऊपर दिए गए हमारे कोड ने इस अधिभार को क्यों चुना, और न कि unordered_map :: value_type अधिभार के रूप में शायद सबसे अधिक उम्मीद होगी?

क्योंकि आप, पहले कई अन्य लोगों के रूप value_typeमें, कंटेनर में गलत व्याख्या की गई थी। value_typeकी *map(चाहे आदेश दिया है या एक अव्यवस्थित) है pair<const K, T>आपके मामले में है जो, pair<const int, shared_ptr<int>>। मिलान न करने का प्रकार उस अधिभार को समाप्त करता है जिसकी आप अपेक्षा कर रहे हैं:

iterator       insert(const_iterator hint, const value_type& obj);

मुझे अभी भी नहीं पता है कि यह नया लेम्मा क्यों मौजूद है, "सार्वभौमिक संदर्भ", वास्तव में कुछ भी विशिष्ट का मतलब नहीं है, और न ही यह एक ऐसी चीज के लिए एक अच्छा नाम है जो व्यवहार में "सार्वभौमिक" नहीं है। कुछ पुराने ढहने वाले नियमों को याद रखना बेहतर है जो C ++ मेटा-भाषा व्यवहार का हिस्सा हैं क्योंकि यह तब से था जब भाषा में टेम्प्लेट पेश किए गए थे, और C ++ 11 मानक से एक नया हस्ताक्षर। वैसे भी इस सार्वभौमिक संदर्भ के बारे में बात करने का क्या लाभ है? वहाँ पहले से ही नाम और चीजें हैं जो काफी अजीब हैं, जैसे std::moveकि वह कुछ भी स्थानांतरित नहीं करता है।
user2485710

2
@ user2485710 कभी-कभी, अज्ञानता परमानंद है, और सभी संदर्भों के ढहने और टेम्पलेट प्रकार कटौती पर छतरी शब्द "सार्वभौमिक संदर्भ" होने के कारण, जो IMHO है, बहुत ही अनपेक्षित है, साथ ही std::forwardउस ट्विक को असली काम करने के लिए उपयोग करने की आवश्यकता है ... स्कॉट मेयर्स अग्रेषण (सार्वभौमिक संदर्भों का उपयोग) के लिए बहुत ही सरल नियमों की स्थापना करते हुए एक अच्छा काम किया है।
मार्क गार्सिया

1
मेरी सोच में, "सार्वभौमिक संदर्भ" फ़ंक्शन टेम्प्लेट मापदंडों को घोषित करने के लिए एक पैटर्न है जो अंतराल और अंतराल दोनों को बांध सकता है। "संदर्भ ढहने" क्या होता है जब "सार्वभौमिक संदर्भ" स्थितियों और अन्य संदर्भों में परिभाषाओं में प्रतिस्थापन (घटा या निर्दिष्ट) टेम्पलेट पैरामीटर होता है।
15

2
@aschepler: सार्वभौमिक संदर्भ केवल एक फैंसी नाम है जो संदर्भ के सबसेट के लिए एक संक्षिप्त नाम है। मैं मानता हूं कि अज्ञानता आनंद है , और यह भी तथ्य यह है कि एक फैंसी नाम होने से इसके बारे में बात करना आसान और ट्रेंडी है और यह व्यवहार को फैलाने में मदद कर सकता है। यही कारण है कि किया जा रहा है कहा कि मैं नाम का बहुत बड़ा प्रशंसक नहीं हूँ, के रूप में यह एक कोने जहाँ वे करने की आवश्यकता नहीं है के लिए वास्तविक नियम धक्का जाना जाता ... है कि जब तक आप क्या स्कॉट Meyers वर्णित का मामला बाहर मारा।
डेविड रोड्रिगेज -

अब व्यर्थ शब्दार्थ, लेकिन मैं जो सुझाव देने का प्रयास कर रहा था: सार्वभौमिक संदर्भ तब होता है जब मैं डिजाइन कर रहा हूं और फ़ंक्शन पैरामीटर को deducible-टेम्पलेट-पैरामीटर के रूप में घोषित करता हूं &&; संदर्भ ढहने तब होता है जब एक कंपाइलर एक टेम्पलेट को इंस्टेंट करता है। संदर्भ ढहने का कारण सार्वभौमिक संदर्भ कार्य है, लेकिन मेरा मस्तिष्क दो शब्दों को एक ही डोमेन में रखना पसंद नहीं करता है।
21
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.