मानचित्र में सम्मिलित करने के लिए पसंदीदा / मुहावरेदार तरीका क्या है?


111

मैंने तत्वों को डालने के चार अलग-अलग तरीकों की पहचान की है std::map:

std::map<int, int> function;

function[0] = 42;
function.insert(std::map<int, int>::value_type(0, 42));
function.insert(std::pair<int, int>(0, 42));
function.insert(std::make_pair(0, 42));

उनमें से कौन सा पसंदीदा / मुहावरेदार तरीका है? (और क्या कोई और तरीका है जो मैंने नहीं सोचा है?)


26
आपके नक्शे को "उत्तर" कहा जाना चाहिए, "फ़ंक्शन" नहीं
विंसेंट रॉबर्ट

2
@Vincent: एचएम? एक फ़ंक्शन मूल रूप से दो सेटों के बीच का एक मानचित्र होता है।
15

7
@FredOverflow: लगता है विन्सेन्ट की टिप्पणी थोड़े किताब के बारे में थोड़े मजाक है ...
विक्टर सोरोकिन

1
ऐसा लगता है कि मूल के विपरीत - 42 एक साथ (ए) जीवन, ब्रह्मांड और सब कुछ का जवाब नहीं हो सकता है, और (बी) कुछ भी नहीं। लेकिन फिर आप जीवन, ब्रह्मांड और सब कुछ एक अंतर के रूप में कैसे व्यक्त करते हैं?
स्टुअर्ट गोलोडेत्ज़

19
@sgolodetz आप एक इंट बड़े के साथ सब कुछ व्यक्त कर सकते हैं।
याकोव गल्का

जवाबों:


90

सबसे पहले, operator[]और insertसदस्य कार्य कार्यात्मक रूप से समतुल्य नहीं हैं:

  • operator[]होगा खोज कुंजी के लिए, एक सम्मिलित डिफ़ॉल्ट निर्माण मूल्य न मिलने पर, और एक संदर्भ जो करने के लिए आप एक मूल्य निर्दिष्ट लौट आते हैं। जाहिर है, यह अक्षम हो सकता है अगर mapped_typeडिफ़ॉल्ट रूप से निर्मित और असाइन किए गए के बजाय सीधे आरंभीकृत होने से लाभ हो सकता है। इस विधि से यह निर्धारित करना भी असंभव हो जाता है कि क्या प्रविष्टि वास्तव में हुई है या यदि आपने केवल पहले से डाली गई कुंजी के लिए मान को अधिलेखित कर दिया है तो
  • insertयदि कुंजी पहले से मानचित्र में मौजूद है सदस्य समारोह कोई असर नहीं करेंगे और, हालांकि यह अक्सर भुला दिया जाता है, रिटर्न एक std::pair<iterator, bool>जो ब्याज की हो सकता है (सबसे विशेष रूप निर्धारित करने के लिए करता है, तो प्रविष्टि वास्तव में किया गया है)।

कॉल करने के लिए सभी सूचीबद्ध संभावनाओं से insert, तीनों लगभग समान हैं। एक अनुस्मारक के रूप में, आइए insertमानक में हस्ताक्षर देखें:

typedef pair<const Key, T> value_type;

  /* ... */

pair<iterator, bool> insert(const value_type& x);

तो तीन कॉल अलग कैसे हैं?

  • std::make_pairटेम्पलेट तर्क कटौती पर निर्भर करता है और (इस मामले में ) value_typeनक्शे के वास्तविक से कुछ अलग प्रकार का उत्पादन कर सकता है , जिसे std::pairबदलने के लिए टेम्पलेट निर्माता को अतिरिक्त कॉल की आवश्यकता होगी value_type(यानी: जोड़ने के constलिए first_type)
  • std::pair<int, int>भी की टेम्पलेट निर्माता के लिए एक अतिरिक्त कॉल की आवश्यकता होगी std::pairक्रम में परिवर्तित करने के लिए पैरामीटर में value_type(यानी: जोड़ने constके लिए first_type)
  • std::map<int, int>::value_typeसंदेह के लिए कोई जगह नहीं छोड़ता है क्योंकि यह सीधे insertसदस्य प्रकार द्वारा अपेक्षित पैरामीटर प्रकार है ।

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


क्या key से const में रूपांतरण करना make_pair () में वास्तव में एक अन्य फ़ंक्शन कॉल का अनुरोध करता है? ऐसा लगता है कि एक निहित कलाकार पर्याप्त होगा जो संकलक को ऐसा करने में प्रसन्न होना चाहिए।
गैलेक्टिका

99

C ++ 11 के अनुसार, आपके पास दो प्रमुख अतिरिक्त विकल्प हैं। सबसे पहले, आप insert()सूची आरंभीकरण सिंटैक्स के साथ उपयोग कर सकते हैं :

function.insert({0, 42});

यह कार्यात्मक रूप से इसके बराबर है

function.insert(std::map<int, int>::value_type(0, 42));

लेकिन बहुत अधिक संक्षिप्त और पठनीय। जैसा कि अन्य उत्तरों ने उल्लेख किया है, इसके अन्य रूपों पर कई फायदे हैं:

  • operator[]दृष्टिकोण, आबंटित होने के लिए मैप किया प्रकार की आवश्यकता है जो हमेशा मामला नहीं है।
  • operator[]दृष्टिकोण मौजूदा तत्वों के ऊपर लिख, और आप कोई रास्ता नहीं बताने के लिए यह हो गया है कि क्या देता है सकते हैं।
  • insertआपके द्वारा सूचीबद्ध अन्य रूपों में एक अंतर्निहित प्रकार रूपांतरण शामिल है, जो आपके कोड को धीमा कर सकता है।

मुख्य दोष यह है कि इस फॉर्म का उपयोग करने के लिए कुंजी और मूल्य की आवश्यकता होती है, इसलिए यह उदाहरण के साथ मैप के साथ काम नहीं करेगा unique_ptr। यह मानक में तय किया गया है, लेकिन फिक्स आपके मानक पुस्तकालय कार्यान्वयन तक नहीं पहुंच सका है।

दूसरा, आप emplace()विधि का उपयोग कर सकते हैं :

function.emplace(0, 42);

यह किसी भी रूप की तुलना में अधिक संक्षिप्त है insert(), चाल-प्रकार के प्रकारों के साथ ठीक काम करता है unique_ptr, और सैद्धांतिक रूप से थोड़ा अधिक कुशल हो सकता है (हालांकि एक सभ्य संकलक अंतर को दूर करना चाहिए)। एकमात्र बड़ी कमी यह है कि यह आपके पाठकों को थोड़ा आश्चर्यचकित कर सकता है, क्योंकि emplaceआमतौर पर तरीकों का उपयोग उस तरह से नहीं किया जाता है।



11

पहला संस्करण:

function[0] = 42; // version 1

42 मान को मानचित्र में सम्मिलित कर सकते हैं या नहीं भी कर सकते हैं। यदि कुंजी 0मौजूद है, तो यह उस कुंजी के लिए 42 को असाइन करेगा, जो भी उस कुंजी का मूल्य है। अन्यथा यह कुंजी / मूल्य जोड़ी को सम्मिलित करता है।

सम्मिलित कार्य:

function.insert(std::map<int, int>::value_type(0, 42));  // version 2
function.insert(std::pair<int, int>(0, 42));             // version 3
function.insert(std::make_pair(0, 42));                  // version 4

दूसरी ओर, यदि 0मानचित्र में कुंजी पहले से मौजूद है तो कुछ भी न करें। यदि कुंजी मौजूद नहीं है, तो यह कुंजी / मूल्य युग्म सम्मिलित करता है।

तीन सम्मिलित कार्य लगभग समान हैं। std::map<int, int>::value_typeके typedefलिए है std::pair<const int, int>, और std::make_pair()स्पष्ट रूप std::pair<>से टेम्पलेट कटौती जादू के माध्यम से पैदा करता है । हालाँकि, अंतिम परिणाम, संस्करण 2, 3 और 4 के लिए समान होना चाहिए।

मैं कौन सा उपयोग करूंगा? मैं व्यक्तिगत रूप से संस्करण 1 पसंद करता हूं; यह संक्षिप्त और "प्राकृतिक" है। बेशक, यदि इसका अधिलेखित व्यवहार वांछित नहीं है, तो मैं संस्करण 4 को पसंद करूंगा, क्योंकि इसके लिए संस्करणों 2 से कम टाइपिंग की आवश्यकता होती है और 3. मुझे नहीं पता कि कुंजी / मान जोड़े में सम्मिलित करने का कोई वास्तविक तरीका है या नहीं std::map

इसके निर्माणकर्ताओं में से एक के माध्यम से मानचित्र में मान सम्मिलित करने का दूसरा तरीका:

std::map<int, int> quadratic_func;

quadratic_func[0] = 0;
quadratic_func[1] = 1;
quadratic_func[2] = 4;
quadratic_func[3] = 9;

std::map<int, int> my_func(quadratic_func.begin(), quadratic_func.end());

5

यदि आप कुंजी 0 के साथ तत्व को अधिलेखित करना चाहते हैं

function[0] = 42;

अन्यथा:

function.insert(std::make_pair(0, 42));

5

चूंकि C ++ 17 std::map दो नए सम्मिलन के तरीके प्रदान करता है: insert_or_assign()और try_emplace(), जैसा कि sp2danny द्वारा टिप्पणी में भी उल्लेख किया गया है ।

insert_or_assign()

असल में, insert_or_assign()का "सुधार" संस्करण है operator[]। इसके विपरीत operator[], insert_or_assign()डिफ़ॉल्ट रूप से निर्माण के लिए मानचित्र के मान प्रकार की आवश्यकता नहीं होती है। उदाहरण के लिए, निम्न कोड संकलित नहीं करता है, क्योंकि MyClassएक डिफ़ॉल्ट निर्माता नहीं है:

class MyClass {
public:
    MyClass(int i) : m_i(i) {};
    int m_i;
};

int main() {
    std::map<int, MyClass> myMap;

    // VS2017: "C2512: 'MyClass::MyClass' : no appropriate default constructor available"
    // Coliru: "error: no matching function for call to 'MyClass::MyClass()"
    myMap[0] = MyClass(1);

    return 0;
}

हालाँकि, यदि आप myMap[0] = MyClass(1);निम्न पंक्ति से प्रतिस्थापित करते हैं, तो कोड संकलित करता है और प्रविष्टि उद्देश्य के अनुसार होती है:

myMap.insert_or_assign(0, MyClass(1));

इसके अलावा, के लिए इसी तरह insert(), insert_or_assign()एक रिटर्न pair<iterator, bool>। बूलियन मान trueएक प्रविष्टि हुआ है और falseयदि एक असाइनमेंट किया गया था। पुनरावृत्त उस तत्व को इंगित करता है जिसे सम्मिलित या अद्यतन किया गया था।

try_emplace()

उपरोक्त के समान, try_emplace()का एक "सुधार" है emplace()। इसके विपरीत emplace(), try_emplace()यदि नक्शे में पहले से मौजूद एक कुंजी के कारण सम्मिलन विफल हो जाता है, तो इसके तर्कों को संशोधित नहीं करता है। उदाहरण के लिए, निम्नलिखित कोड एक कुंजी के साथ एक तत्व को निकालने का प्रयास करता है जो पहले से ही नक्शे में संग्रहीत है (देखें *):

int main() {
    std::map<int, std::unique_ptr<MyClass>> myMap2;
    myMap2.emplace(0, std::make_unique<MyClass>(1));

    auto pMyObj = std::make_unique<MyClass>(2);    
    auto [it, b] = myMap2.emplace(0, std::move(pMyObj));  // *

    if (!b)
        std::cout << "pMyObj was not inserted" << std::endl;

    if (pMyObj == nullptr)
        std::cout << "pMyObj was modified anyway" << std::endl;
    else
        std::cout << "pMyObj.m_i = " << pMyObj->m_i <<  std::endl;

    return 0;
}

आउटपुट (कम से कम VS2017 और कोलिरु के लिए):

pMyObj डाला नहीं गया था
pMyObj को वैसे भी संशोधित किया गया था

जैसा कि आप देख सकते हैं, pMyObjअब मूल वस्तु की ओर इशारा नहीं करता है। हालाँकि, यदि आप auto [it, b] = myMap2.emplace(0, std::move(pMyObj));निम्न कोड से प्रतिस्थापित करते हैं, तो आउटपुट अलग दिखता है, क्योंकि pMyObjअपरिवर्तित रहता है:

auto [it, b] = myMap2.try_emplace(0, std::move(pMyObj));

आउटपुट:

pMyObj डाला नहीं गया था
pMyObj pMyObj.m_i = 2

कोलीरू पर कोड

कृपया ध्यान दें: मैंने अपने स्पष्टीकरण को इस उत्तर में फिट करने के लिए यथासंभव संक्षिप्त और सरल रखने की कोशिश की। अधिक सटीक और व्यापक विवरण के लिए, मैं धाराप्रवाह सी ++ पर इस लेख को पढ़ने की सलाह देता हूं ।


3

मैं उपरोक्त संस्करणों के बीच कुछ समय की तुलना कर रहा हूं:

function[0] = 42;
function.insert(std::map<int, int>::value_type(0, 42));
function.insert(std::pair<int, int>(0, 42));
function.insert(std::make_pair(0, 42));

उस समय के टर्न आउट संस्करण के बीच अंतर छोटे हैं।

#include <map>
#include <vector>
#include <boost/date_time/posix_time/posix_time.hpp>
using namespace boost::posix_time;
class Widget {
public:
    Widget() {
        m_vec.resize(100);
        for(unsigned long it = 0; it < 100;it++) {
            m_vec[it] = 1.0;
        }
    }
    Widget(double el)   {
        m_vec.resize(100);
        for(unsigned long it = 0; it < 100;it++) {
            m_vec[it] = el;
        }
    }
private:
    std::vector<double> m_vec;
};


int main(int argc, char* argv[]) {



    std::map<int,Widget> map_W;
    ptime t1 = boost::posix_time::microsec_clock::local_time();    
    for(int it = 0; it < 10000;it++) {
        map_W.insert(std::pair<int,Widget>(it,Widget(2.0)));
    }
    ptime t2 = boost::posix_time::microsec_clock::local_time();
    time_duration diff = t2 - t1;
    std::cout << diff.total_milliseconds() << std::endl;

    std::map<int,Widget> map_W_2;
    ptime t1_2 = boost::posix_time::microsec_clock::local_time();    
    for(int it = 0; it < 10000;it++) {
        map_W_2.insert(std::make_pair(it,Widget(2.0)));
    }
    ptime t2_2 = boost::posix_time::microsec_clock::local_time();
    time_duration diff_2 = t2_2 - t1_2;
    std::cout << diff_2.total_milliseconds() << std::endl;

    std::map<int,Widget> map_W_3;
    ptime t1_3 = boost::posix_time::microsec_clock::local_time();    
    for(int it = 0; it < 10000;it++) {
        map_W_3[it] = Widget(2.0);
    }
    ptime t2_3 = boost::posix_time::microsec_clock::local_time();
    time_duration diff_3 = t2_3 - t1_3;
    std::cout << diff_3.total_milliseconds() << std::endl;

    std::map<int,Widget> map_W_0;
    ptime t1_0 = boost::posix_time::microsec_clock::local_time();    
    for(int it = 0; it < 10000;it++) {
        map_W_0.insert(std::map<int,Widget>::value_type(it,Widget(2.0)));
    }
    ptime t2_0 = boost::posix_time::microsec_clock::local_time();
    time_duration diff_0 = t2_0 - t1_0;
    std::cout << diff_0.total_milliseconds() << std::endl;

    system("pause");
}

यह संस्करणों के लिए क्रमशः देता है (मैंने फ़ाइल को 3 बार चलाया, इसलिए प्रत्येक के लिए 3 लगातार अंतर):

map_W.insert(std::pair<int,Widget>(it,Widget(2.0)));

2198 एमएस, 2078 एमएस, 2072 एमएस

map_W_2.insert(std::make_pair(it,Widget(2.0)));

2290 एमएस, 2037 एमएस, 2046 एमएस

 map_W_3[it] = Widget(2.0);

2592 एमएस, 2278 एमएस, 2296 एमएस

 map_W_0.insert(std::map<int,Widget>::value_type(it,Widget(2.0)));

2234 एमएस, 2031 एमएस, 2027 एमएस

इसलिए, विभिन्न सम्मिलित संस्करणों के बीच के परिणामों की उपेक्षा की जा सकती है (हालांकि एक परिकल्पना परीक्षण नहीं किया है)!

map_W_3[it] = Widget(2.0);संस्करण विजेट के लिए डिफ़ॉल्ट निर्माता के साथ एक प्रारंभ की वजह से इस उदाहरण के लिए 10-15% अधिक समय के बारे में लेता है।


2

संक्षेप में, []ऑपरेटर मूल्यों को अपडेट करने के लिए अधिक कुशल है क्योंकि इसमें वैल्यू टाइप के डिफॉल्ट कंस्ट्रक्टर को कॉल करना और फिर इसे एक नया मान प्रदान करना शामिल है, जबकि insert()वैल्यूज को जोड़ने के लिए यह अधिक कुशल है।

प्रभावी एसटीएल से उद्धृत स्निपेट : स्कॉट मेयर्स द्वारा मानक टेम्पलेट लाइब्रेरी के आपके उपयोग को बेहतर बनाने के 50 विशिष्ट तरीके , मद 24 मदद कर सकते हैं।

template<typename MapType, typename KeyArgType, typename ValueArgType>
typename MapType::iterator
insertKeyAndValue(MapType& m, const KeyArgType&k, const ValueArgType& v)
{
    typename MapType::iterator lb = m.lower_bound(k);

    if (lb != m.end() && !(m.key_comp()(k, lb->first))) {
        lb->second = v;
        return lb;
    } else {
        typedef typename MapType::value_type MVT;
        return m.insert(lb, MVT(k, v));
    }
}

आप इसका एक सामान्य-प्रोग्रामिंग-मुक्त संस्करण चुनने का निर्णय ले सकते हैं, लेकिन मुद्दा यह है कि मुझे यह प्रतिमान ('जोड़ने' और 'अद्यतन' को अलग करते हुए) बेहद उपयोगी लगता है।


1

यदि आप std में तत्व सम्मिलित करना चाहते हैं :: मानचित्र - सम्मिलित करें () फ़ंक्शन का उपयोग करें, और यदि आप तत्व ढूंढना चाहते हैं (कुंजी द्वारा) और इसे कुछ असाइन करें - ऑपरेटर का उपयोग करें []।

उपयोग बढ़ाने को सरल बनाने के लिए :: इस तरह से लाइब्रेरी असाइन करें:

using namespace boost::assign;

// For inserting one element:

insert( function )( 0, 41 );

// For inserting several elements:

insert( function )( 0, 41 )( 0, 42 )( 0, 43 );

1

मैं सिर्फ समस्या को थोड़ा बदल देता हूं (तार का नक्शा) डालने का एक और शौक दिखाने के लिए:

std::map<int, std::string> rancking;

rancking[0] = 42;  // << some compilers [gcc] show no error

rancking.insert(std::pair<int, std::string>(0, 42));// always a compile error

यह तथ्य कि संकलक "रेकिंग [1] = 42;" पर कोई त्रुटि नहीं दिखाता है विनाशकारी प्रभाव हो सकता है!


कंपाइलर पूर्व के लिए कोई त्रुटि नहीं दिखाता है क्योंकि std::string::operator=(char)मौजूद है, लेकिन वे बाद के लिए एक त्रुटि दिखाते हैं क्योंकि निर्माता std::string::string(char)मौजूद नहीं है। यह एक त्रुटि उत्पन्न नहीं करना चाहिए क्योंकि C ++ हमेशा स्वतंत्र रूप से किसी भी पूर्णांक-शैली के शाब्दिक रूप में व्याख्या करता है char, इसलिए यह संकलक बग नहीं है, बल्कि इसके बजाय एक प्रोग्रामर त्रुटि है। मूल रूप से, मैं सिर्फ इतना कह रहा हूं कि आपके कोड में बग का परिचय देना या न होना एक ऐसी चीज है जिसे आपको अपने लिए देखना होगा। BTW, आप प्रिंट कर सकते हैं rancking[0]और ASCII का उपयोग कर एक कंपाइलर आउटपुट करेंगे *, जो है (char)(42)
कीथ एम

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