एसटीएल नक्शे में, नक्शे का उपयोग करना बेहतर होता है :: [की तुलना में सम्मिलित करें]?


201

कुछ समय पहले, मैंने एसटीएल के नक्शे में मूल्यों को सम्मिलित करने के बारे में एक सहयोगी के साथ चर्चा की थी । मैंने वरीयता दिया map[key] = value; क्योंकि यह स्वाभाविक लगता है और पढ़ने के लिए स्पष्ट है जबकि उन्होंने पसंद किया map.insert(std::make_pair(key, value))

मैंने उनसे पूछा और न तो हम यह बता सकते हैं कि डालने का कारण बेहतर क्यों है, लेकिन मुझे यकीन है कि यह सिर्फ एक शैली की प्राथमिकता नहीं थी, बल्कि दक्षता जैसे तकनीकी कारण भी थे। एसजीआई एसटीएल संदर्भ बस कहते हैं, "सच पूछिये तो, इस सदस्य समारोह अनावश्यक है। यह केवल सुविधा के लिए मौजूद है"

क्या कोई मुझे वह कारण बता सकता है, या मैं सिर्फ एक ही सपना देख रहा हूं?


2
सभी महान प्रतिक्रियाओं के लिए धन्यवाद - वे वास्तव में मददगार रहे हैं। यह स्टैक ओवरफ्लो का एक बेहतरीन डेमो है। मुझे फट गया था जिसके लिए स्वीकृत उत्तर होना चाहिए: नेटजेफ़ विभिन्न व्यवहार के बारे में अधिक स्पष्ट है, ग्रेग रोजर्स ने प्रदर्शन के मुद्दों का उल्लेख किया। काश मैं दोनों टिक पाता।
danio

6
वास्तव में, C ++ 11 के साथ, आप संभवतः मानचित्र का उपयोग करने के लिए सर्वश्रेष्ठ हैं :: एमप्ले जो दोहरे निर्माण से बचा जाता है
ईनपोकलम

@einpoklum: वास्तव में, स्कॉट मेयर्स ने अपनी बात "अन्यथा प्रभावी सी ++ के लिए विकसित खोज" में अन्यथा सुझाव दिया है।
थॉमस ईडिंग

3
@einpoklum: यह मामला है जब नव निर्मित स्मृति में जगह है। लेकिन नक्शे के लिए कुछ मानकों की आवश्यकताओं के कारण, तकनीकी कारण हैं कि एमप्लेस को सम्मिलित करने की तुलना में धीमा क्यों किया जा सकता है। यह बात youtube पर स्वतंत्र रूप से उपलब्ध है, जैसे कि यह लिंक youtube.com/watch?v=smqT9Io_bKo @ ~ 38-40 min mark है। एक SO लिंक के लिए, यहाँ stackoverflow.com/questions/26446352/…
थॉमस Eding

1
मैं वास्तव में मेयर्स द्वारा प्रस्तुत कुछ के साथ बहस करूंगा, लेकिन यह इस टिप्पणी के दायरे से परे है और वैसे भी, मुझे लगता है कि मुझे अपनी पिछली टिप्पणी को वापस लेना होगा।
einpoklum

जवाबों:


240

जब आप लिखते हैं

map[key] = value;

यह बताने का कोई तरीका नहीं है कि आपने प्रतिस्थापित किया valueहै key, या यदि आप बनाया एक नया keyके साथ value

map::insert() केवल बनाएंगे:

using std::cout; using std::endl;
typedef std::map<int, std::string> MyMap;
MyMap map;
// ...
std::pair<MyMap::iterator, bool> res = map.insert(MyMap::value_type(key,value));
if ( ! res.second ) {
    cout << "key " <<  key << " already exists "
         << " with value " << (res.first)->second << endl;
} else {
    cout << "created key " << key << " with value " << value << endl;
}

मेरे अधिकांश ऐप्स के लिए, मैं आमतौर पर परवाह नहीं करता हूं कि मैं बना रहा हूं या बदल रहा हूं, इसलिए मैं पढ़ने में आसान का उपयोग करता हूं map[key] = value


16
ध्यान दिया जाना चाहिए कि नक्शा :: डालने कभी भी मूल्यों को प्रतिस्थापित नहीं करता है। और सामान्य मामले में मैं कहूंगा कि दूसरे मामले में भी (res.first)->secondइसके बजाय उपयोग करना बेहतर है value
dalle

1
मैंने उस नक्शे को और अधिक स्पष्ट होने के लिए अद्यतन किया: सम्मिलित करें कभी नहीं बदलता है। मैंने छोड़ दिया elseक्योंकि मुझे लगता है कि उपयोग करने से valueयह पुनरावृत्ति की तुलना में स्पष्ट है। केवल तभी यदि मान के प्रकार में एक असामान्य प्रतिलिपि ctor या op == होता, तो यह भिन्न होता, और वह प्रकार STL कंटेनरों का उपयोग करके अन्य मुद्दों का कारण बनता।
नेटजेफ

1
map.insert(std::make_pair(key,value))होना चाहिए map.insert(MyMap::value_type(key,value))। प्रकार से लौटाए गए प्रकार से make_pairमेल नहीं खाता है insertऔर वर्तमान समाधान में रूपांतरण की आवश्यकता है
डेविड रॉड्रिग्ज़ - dribeas

1
यह बताने का एक तरीका है कि क्या आपने डाला है या सिर्फ सौंपा गया है operator[], बस पहले और बाद के आकार की तुलना करें। Imho map::operator[]केवल डिफ़ॉल्ट रचनात्मक प्रकारों के लिए कॉल करने में सक्षम होने के लिए अधिक महत्वपूर्ण है।
आईडीक्लेव ४६३०३५18१

@ DavidRodríguez-dribeas: अच्छा सुझाव है, मैंने अपने उत्तर में कोड अपडेट किया है।
netjeff

53

जब मानचित्र में पहले से मौजूद कुंजी की बात आती है, तो दोनों के पास अलग-अलग शब्दार्थ होते हैं। तो वे वास्तव में सीधे तुलनीय नहीं हैं।

लेकिन ऑपरेटर [] संस्करण के लिए डिफ़ॉल्ट रूप से मान का निर्माण करना आवश्यक है, और फिर असाइन करना, इसलिए यदि यह अधिक महंगा है तो प्रतिलिपि निर्माण करें, तो यह अधिक महंगा होगा। कभी-कभी डिफ़ॉल्ट निर्माण का कोई मतलब नहीं होता है, और फिर ऑपरेटर [] संस्करण का उपयोग करना असंभव होगा।


1
make_pair को कॉपी कंस्ट्रक्टर की आवश्यकता हो सकती है - जो डिफ़ॉल्ट रूप से खराब होगा। वैसे भी +1।

1
मुख्य बात यह है, जैसा कि आपने कहा, कि उनके पास अलग-अलग शब्दार्थ हैं। तो न तो दूसरे से बेहतर है, बस उसी का उपयोग करें जो आपको चाहिए।
jalf

कॉपी कंस्ट्रक्टर असाइनमेंट के बाद डिफॉल्ट कंस्ट्रक्टर से बदतर क्यों होगा? यदि ऐसा है, तो क्लास लिखने वाले को कुछ याद नहीं है, क्योंकि जो भी ऑपरेटर = करता है, उन्हें कॉपी कंस्ट्रक्टर में ही करना चाहिए।
स्टीव जेसप

1
कभी-कभी डिफ़ॉल्ट निर्माण उतना ही महंगा होता है जितना कि असाइनमेंट। स्वाभाविक रूप से असाइनमेंट और कॉपी निर्माण समकक्ष होगा।
ग्रेग रोजर्स 15

@ अरकडी एक अनुकूलित बिल्ड में, कंपाइलर अक्सर कई अनावश्यक कॉपी कंस्ट्रक्टर कॉल्स को हटा देगा।
विरोध किया

35

एक और बात पर ध्यान दें std::map :

myMap[nonExistingKey]; के मानचित्र में एक नई प्रविष्टि बनाएगा, जिसे कुंजीबद्ध किया गया है nonExistingKeyडिफ़ॉल्ट मान आरंभिक रूप से ।

इसने मुझे नरक से पहली बार डराया, जब मैंने इसे देखा (एक बुरी तरह से विरासत बग के खिलाफ अपना सिर पीटते हुए)। उम्मीद नहीं की होगी। मेरे लिए, यह एक ऑपरेशन जैसा दिखता है, और मुझे "साइड-इफ़ेक्ट" की उम्मीद नहीं थी। map.find()अपने नक्शे से प्राप्त करते समय प्राथमिकता दें ।


3
यह एक सभ्य दृष्टिकोण है, हालांकि इस प्रारूप के लिए हैश मैप बहुत सार्वभौमिक हैं। यह उन "विषमताओं में से एक हो सकता है जो किसी को नहीं लगता है कि यह अजीब है" सिर्फ इसलिए कि वे समान सम्मेलनों का कितना व्यापक उपयोग करते हैं
स्टीफन जे

19

यदि डिफ़ॉल्ट निर्माता का प्रदर्शन हिट नहीं है, तो कृपया, भगवान के प्यार के लिए, अधिक पठनीय संस्करण के साथ जाएं।

:)


5
दूसरा! यह चिह्नित करना होगा। बहुत से लोग नैनो-सेकंड स्पीडअप के लिए आपत्तिजनक व्यवहार करते हैं। हम पर दया करो गरीबों की आत्माएं जो इस तरह के अत्याचारों को बनाए रखना चाहिए!
18

6
जैसा कि ग्रेग रोजर्स ने लिखा है: "दोनों के अलग-अलग शब्दार्थ हैं जब यह मानचित्र में पहले से मौजूद कुंजी की बात आती है। इसलिए वे वास्तव में सीधे तुलनीय नहीं हैं।"
dalle

यह एक पुराना सवाल और जवाब है। लेकिन "अधिक पठनीय संस्करण" एक बेवकूफ कारण है। क्योंकि जो सबसे अधिक पठनीय है, वह व्यक्ति पर निर्भर करता है।
वालेंटिन

14

insert अपवाद सुरक्षा के दृष्टिकोण से बेहतर है।

अभिव्यक्ति map[key] = valueवास्तव में दो ऑपरेशन हैं:

  1. map[key] - डिफ़ॉल्ट मान के साथ एक नक्शा तत्व बनाना।
  2. = value - मान को उस तत्व में कॉपी करना।

दूसरे चरण में एक अपवाद हो सकता है। परिणामस्वरूप ऑपरेशन केवल आंशिक रूप से किया जाएगा (एक नए तत्व को मानचित्र में जोड़ा गया था, लेकिन उस तत्व के साथ प्रारंभ नहीं किया गया थाvalue )। वह स्थिति जब कोई ऑपरेशन पूरा नहीं होता है, लेकिन सिस्टम स्थिति को संशोधित किया जाता है, इसे "साइड इफेक्ट" के साथ ऑपरेशन कहा जाता है।

insertऑपरेशन एक मजबूत गारंटी देता है, इसका मतलब है कि इसके दुष्प्रभाव नहीं हैं ( https://en.wikipedia.org/wiki/Exception_safety )।insertया तो पूरी तरह से किया जाता है या यह मानचित्र को असंशोधित स्थिति में छोड़ देता है।

http://www.cplusplus.com/reference/map/map/insert/ :

यदि एक भी तत्व सम्मिलित किया जाना है, तो अपवाद (मजबूत गारंटी) के मामले में कंटेनर में कोई बदलाव नहीं हैं।


1
अधिक महत्वपूर्ण, सम्मिलित करने के लिए डिफ़ॉल्ट रूप से मूल्यवान होने के लिए मूल्य की आवश्यकता नहीं होती है।
उमनोयोब

13

यदि आपका आवेदन गति महत्वपूर्ण है तो मैं [] ऑपरेटर का उपयोग करने की सलाह दूंगा क्योंकि यह मूल वस्तु की कुल 3 प्रतियां बनाता है जिसमें से 2 अस्थायी वस्तुएं हैं और जितनी जल्दी या बाद में नष्ट हो जाती हैं।

लेकिन सम्मिलित () में, मूल वस्तु की 4 प्रतियां बनाई जाती हैं, जिनमें से 3 अस्थायी वस्तुएं होती हैं (जरूरी नहीं कि "अस्थायी") और नष्ट हो जाएं।

जिसका मतलब है कि इसके लिए अतिरिक्त समय: 1. एक वस्तु स्मृति आवंटन 2. एक अतिरिक्त निर्माणकर्ता कॉल 3. एक अतिरिक्त विध्वंसक कॉल 4. एक वस्तु स्मृति आबंटन

यदि आपकी वस्तुएं बड़ी हैं, तो निर्माणकर्ता विशिष्ट हैं, विध्वंसक बहुत सारे संसाधन मुक्त करते हैं, ऊपर दिए गए अंक और भी अधिक गिनते हैं। पठनीयता के बारे में, मुझे लगता है कि दोनों पर्याप्त रूप से उचित हैं।

मेरे दिमाग में यही सवाल आया लेकिन पठनीयता नहीं बल्कि गति। यहाँ एक नमूना कोड है जिसके माध्यम से मुझे उस बिंदु के बारे में पता चला जिसका मैंने उल्लेख किया था।

class Sample
{
    static int _noOfObjects;

    int _objectNo;
public:
    Sample() :
        _objectNo( _noOfObjects++ )
    {
        std::cout<<"Inside default constructor of object "<<_objectNo<<std::endl;
    }

    Sample( const Sample& sample) :
    _objectNo( _noOfObjects++ )
    {
        std::cout<<"Inside copy constructor of object "<<_objectNo<<std::endl;
    }

    ~Sample()
    {
        std::cout<<"Destroying object "<<_objectNo<<std::endl;
    }
};
int Sample::_noOfObjects = 0;


int main(int argc, char* argv[])
{
    Sample sample;
    std::map<int,Sample> map;

    map.insert( std::make_pair<int,Sample>( 1, sample) );
    //map[1] = sample;
    return 0;
}

डालने पर आउटपुट () का उपयोग किया जाता है जब [] ऑपरेटर का उपयोग किया जाता है तो आउटपुट


4
अब उस परीक्षण को फिर से चलाएं जिसमें पूर्ण अनुकूलन सक्षम हो।
विरोध किया

2
इसके अलावा, विचार करें कि वास्तव में ऑपरेटर क्या करता है []। यह पहली बार एक प्रविष्टि के लिए मानचित्र खोजता है जो निर्दिष्ट कुंजी से मेल खाता है। यदि यह एक पाता है, तो यह उस निर्दिष्ट के साथ उस प्रविष्टि के मूल्य को ओवरराइट करता है। यदि ऐसा नहीं होता है, तो यह निर्दिष्ट कुंजी और मूल्य के साथ एक नई प्रविष्टि सम्मिलित करता है। आपका नक्शा जितना बड़ा होगा, नक्शे को खोजने में ऑपरेटर [] को उतना ही समय लगेगा। कुछ बिंदु पर, यह अतिरिक्त कॉपी कैटरर कॉल के लिए मेक अप से अधिक होगा (यदि कंपाइलर ने अपने अनुकूलन जादू के बाद भी अंतिम कार्यक्रम में रहता है)।
एंटी

1
@antred, insertएक ही खोज करना है, इसलिए इसमें से कोई अंतर नहीं है [](क्योंकि मानचित्र कुंजी अद्वितीय हैं)।
एस.जे.

प्रिंटआउट के साथ क्या हो रहा है, इसका अच्छा चित्रण - लेकिन कोड के ये 2 बिट्स वास्तव में क्या कर रहे हैं? मूल वस्तु की 3 प्रतियां क्यों आवश्यक हैं?
rbennett485

1. असाइनमेंट ऑपरेटर को लागू करना चाहिए, या आपको विध्वंसक में गलत नंबर मिलेंगे। अतिरिक्त कॉपी-कंस्ट्रक्शन से बचने के लिए जोड़ी का निर्माण करते समय std :: चाल का उपयोग करें "map.insert (std :: make_pair <int, Sample> (1, std:) : चाल (नमूना)); "
Fl0

10

अब सी ++ 11 में मुझे लगता है कि एसटीएल मानचित्र में एक जोड़ी डालने का सबसे अच्छा तरीका है:

typedef std::map<int, std::string> MyMap;
MyMap map;

auto& result = map.emplace(3,"Hello");

परिणाम के साथ एक जोड़ी हो जाएगा:

  • पहला तत्व (result.first), जोड़े गए बिंदु को इंगित करता है या इस कुंजी के साथ जोड़े को इंगित करता है यदि कुंजी पहले से मौजूद है।

  • दूसरा तत्व (result.second), सही अगर प्रविष्टि सही थी या गलत था तो यह कुछ गलत हो गया था।

पुनश्च: यदि आप उस आदेश के बारे में मामला नहीं करते हैं जो आप std का उपयोग कर सकते हैं :: unordered_map;)

धन्यवाद!


9

मैप के साथ एक गोचा :: इंसर्ट () यह है कि यदि मान पहले से मौजूद है तो यह मान को प्रतिस्थापित नहीं करेगा। मैंने जावा प्रोग्रामर द्वारा लिखा गया C ++ कोड देखा है जहाँ उन्होंने Java में मैप्स .put () के समान इंसर्ट करने की अपेक्षा इन्सर्ट () की है जहाँ मान बदले जाते हैं।


2

एक नोट यह है कि आप Boost.Assign का भी उपयोग कर सकते हैं :

using namespace std;
using namespace boost::assign; // bring 'map_list_of()' into scope

void something()
{
    map<int,int> my_map = map_list_of(1,2)(2,3)(3,4)(4,5)(5,6);
}

1

यहां एक और उदाहरण है, जिसमें दिखाया गया है कि यदि मौजूद है तो कुंजी के लिए मान को operator[] अधिलेखित कर देता है, लेकिन मौजूद होने पर मान को .insert अधिलेखित नहीं करता है।

void mapTest()
{
  map<int,float> m;


  for( int i = 0 ; i  <=  2 ; i++ )
  {
    pair<map<int,float>::iterator,bool> result = m.insert( make_pair( 5, (float)i ) ) ;

    if( result.second )
      printf( "%d=>value %f successfully inserted as brand new value\n", result.first->first, result.first->second ) ;
    else
      printf( "! The map already contained %d=>value %f, nothing changed\n", result.first->first, result.first->second ) ;
  }

  puts( "All map values:" ) ;
  for( map<int,float>::iterator iter = m.begin() ; iter !=m.end() ; ++iter )
    printf( "%d=>%f\n", iter->first, iter->second ) ;

  /// now watch this.. 
  m[5]=900.f ; //using operator[] OVERWRITES map values
  puts( "All map values:" ) ;
  for( map<int,float>::iterator iter = m.begin() ; iter !=m.end() ; ++iter )
    printf( "%d=>%f\n", iter->first, iter->second ) ;

}

1

यह एक प्रतिबंधित मामला है, लेकिन मुझे मिली टिप्पणियों से देखते हुए मुझे लगता है कि यह ध्यान देने योग्य है।

मैंने पिछले उपयोग के नक्शे में लोगों को रूप में देखा है

map< const key, const val> Map;

आकस्मिक मूल्य ओवरराइटिंग के मामलों से बचने के लिए, लेकिन फिर कुछ अन्य कोड्स में लिखना आगे बढ़ें:

const_cast< T >Map[]=val;

जैसा कि मुझे याद है ऐसा करने का उनका कारण था क्योंकि उन्हें यकीन था कि कोड के इन निश्चित बिट्स में वे मानचित्र मानों को अधिलेखित करने वाले नहीं थे; इसलिए, अधिक 'पठनीय' विधि के साथ आगे बढ़ना[]

मुझे वास्तव में कभी भी उस कोड से कोई सीधी परेशानी नहीं हुई जो इन लोगों द्वारा लिखी गई थी, लेकिन मैं आज तक दृढ़ता से महसूस करता हूं कि जोखिम - हालांकि छोटे - छोटे नहीं लिए जाने चाहिए, जब उन्हें आसानी से टाला जा सकता है।

उन मामलों में जहां आप मानचित्र मानों के साथ काम कर रहे हैं बिल्कुल ओवरराइट नहीं होने चाहिए , उपयोग करें insert। केवल पठनीयता के लिए अपवाद न करें।


कई अलग-अलग लोगों ने लिखा है कि? निश्चित रूप से उपयोग insert(नहीं input), const_castक्योंकि किसी भी पिछले मूल्य को अधिलेखित करने का कारण होगा, जो बहुत ही गैर-कास्ट है। या, मान प्रकार को चिह्नित न करें const। (इस तरह की बात आमतौर पर अंतिम परिणाम होती है const_cast, इसलिए यह लगभग हमेशा एक लाल झंडा होता है जो कहीं और त्रुटि का संकेत देता है।)
पोटाटोस्वाटर

@Potatoswatter तुम सही हो। मैं बस देख रहा हूं कि const_cast [] का उपयोग कुछ लोगों द्वारा कास्ट मैप वैल्यू के साथ किया जाता है, जब उन्हें यकीन होता है कि वे कोड के कुछ बिट्स में एक पुराने मूल्य को प्रतिस्थापित नहीं करेंगे; चूंकि [] स्वयं अधिक पठनीय है। जैसा कि मैंने अपने उत्तर के अंतिम बिट में उल्लेख किया है, मैं उन insertमामलों में उपयोग करने की सलाह दूंगा जहां आप मूल्यों को अधिलेखित होने से रोकना चाहते हैं। (बस बदल inputकरने के लिए insert- धन्यवाद)
dk123

@Potatoswatter यदि मैं सही ढंग से याद करता हूं, तो लोग जिन मुख्य कारणों का उपयोग करते दिखते हैं const_cast<T>(map[key]), वे थे 1. [] अधिक पठनीय है, 2. वे कोड के कुछ बिट्स में विश्वास करते हैं कि वे मूल्यों को अधिलेखित नहीं करेंगे, और 3. वे नहीं करते हैं अनजाने कोड के अन्य बिट्स उनके मूल्यों को ओवरराइट करना चाहते हैं - इसलिए const value
dk123

2
मैंने ऐसा करने के बारे में कभी नहीं सुना है; अपने इसे कहां देखा था? लेखन const_cast"अतिरिक्त" पठनीयता को नकारने से अधिक लगता है [], और उस तरह का आत्मविश्वास लगभग एक डेवलपर को आग लगाने के लिए पर्याप्त है। मुश्किल रनटाइम स्थितियों को बुलेटप्रूफ डिज़ाइनों द्वारा हल किया जाता है, आंत भावनाओं को नहीं।
पोटाटोसवेटर

@Potatoswatter मुझे याद है कि यह मेरी पिछली नौकरियों में से एक था जो शैक्षिक खेल विकसित कर रहा था। मैं अपनी आदतों को बदलने के लिए कोड लिखने वाले लोगों को कभी नहीं मिला। आप बिल्कुल सही हैं और मैं आपसे दृढ़ता से सहमत हूं। आपकी टिप्पणियों से, मैंने फैसला किया है कि यह संभवतः मेरे मूल उत्तर से अधिक ध्यान देने योग्य है और इसलिए मैंने इसे प्रतिबिंबित करने के लिए इसे अपडेट किया है। धन्यवाद!
dk123

1

तथ्य यह है कि एसटीडी :: मानचित्र insert()फ़ंक्शन कुंजी से जुड़े मूल्य को अधिलेखित नहीं करता है, हमें इस तरह से ऑब्जेक्ट एन्यूमरेशन कोड लिखने की अनुमति देता है:

string word;
map<string, size_t> dict;
while(getline(cin, word)) {
    dict.insert(make_pair(word, dict.size()));
}

यह एक बहुत ही आम समस्या है, जब हमें कुछ आईडी में अलग-अलग गैर-अद्वितीय वस्तुओं को मैप करने की आवश्यकता होती है 0..N। उन आईडी का बाद में उपयोग किया जा सकता है, उदाहरण के लिए, ग्राफ एल्गोरिदम में। operator[]मेरे विचार से वैकल्पिक पढ़ने योग्य होगा:

string word;
map<string, size_t> dict;
while(getline(cin, word)) {
    size_t sz = dict.size();
    if (!dict.count(word))
        dict[word] = sz; 
} 
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.