C ++ 11 की श्रेणी-आधारित का उपयोग करने का सही तरीका क्या है?


211

C ++ 11 के रेंज-आधारित का उपयोग करने का सही तरीका क्या है for?

क्या वाक्यविन्यास का उपयोग किया जाना चाहिए? for (auto elem : container), या for (auto& elem : container)या for (const auto& elem : container)? या कुछ और?


6
समान विचार फ़ंक्शन के तर्क के लिए लागू होता है।
मैक्सिम Egorushkin

3
दरअसल, इसके लिए रेंज-आधारित के साथ बहुत कम है। वही किसी के बारे में कहा जा सकता है auto (const)(&) x = <expr>;
मैथ्यू एम।

2
@ मैथ्यूएमएम: यह निश्चित रूप से रेंज-आधारित के साथ बहुत कुछ करता है! एक शुरुआत पर विचार करें, जो कई सिंटैक्स देखता है और यह नहीं चुन सकता कि किस फॉर्म का उपयोग करना है। "क्यू एंड ए" का उद्देश्य कुछ प्रकाश को बहाने की कोशिश करना था, और कुछ मामलों के अंतर को स्पष्ट करना (और उन मामलों पर चर्चा करना जो ठीक संकलन करते हैं लेकिन बेकार गहरी-प्रतियों, आदि के कारण अक्षम हैं)।
Mr.C64

2
@ Mr.C64: जहां तक ​​मेरा सवाल है, यह autoसामान्य तौर पर रेंज-बेस्ड की तुलना में अधिक है; आप पूरी तरह से बिना किसी के लिए रेंज-आधारित का उपयोग कर सकते हैं auto! for (int i: v) {}पूरी तरह से ठीक है। बेशक, आपके जवाब में आपके द्वाराauto उठाए जाने वाले अधिकांश बिंदुओं के साथ टाइप के साथ अधिक हो सकता है ... लेकिन सवाल से यह स्पष्ट नहीं है कि दर्द बिंदु कहां है। व्यक्तिगत रूप से, मैं autoप्रश्न से हटाने के लिए प्रतिज्ञा करूंगा ; या शायद यह स्पष्ट कर दें कि आप autoकिस प्रकार का उपयोग करते हैं या स्पष्ट रूप से नाम का उपयोग करते हैं , प्रश्न मूल्य / संदर्भ पर केंद्रित है।
मथिउ एम।

1
@ मैथ्यूएमएम: मैं शीर्षक बदलने या किसी ऐसे रूप में प्रश्न को संपादित करने के लिए तैयार हूं जो उन्हें और अधिक स्पष्ट कर सकता है ... फिर, मेरा ध्यान सिंटैक्स के लिए रेंज-आधारित के लिए कई विकल्पों पर चर्चा करना था (कोड दिखाना जो संकलन करता है लेकिन है अक्षम्य, कोड जो संकलन करने में विफल रहता है, आदि) और किसी को (विशेषकर शुरुआती स्तर पर) कुछ मार्गदर्शन देने की कोशिश कर रहा है, जो C ++ 11 रेंज-लूप्स के लिए आधारित है।
मि। C६४

जवाबों:


389

आइए कंटेनर में तत्वों को देखने के बीच अंतर करना शुरू करें। उन्हें जगह में संशोधित करना।

तत्वों का अवलोकन करना

आइए एक सरल उदाहरण पर विचार करें:

vector<int> v = {1, 3, 5, 7, 9};

for (auto x : v)
    cout << x << ' ';

उपरोक्त कोड निम्नलिखित तत्वों को प्रिंट intकरता है vector:

1 3 5 7 9

अब एक और मामले पर विचार करें, जिसमें वेक्टर तत्व केवल सरल पूर्णांक नहीं हैं, बल्कि एक और अधिक जटिल वर्ग के उदाहरण हैं, जिसमें कस्टम कॉपी सेंसर आदि हैं।

// A sample test class, with custom copy semantics.
class X
{
public:
    X() 
        : m_data(0) 
    {}

    X(int data)
        : m_data(data)
    {}

    ~X() 
    {}

    X(const X& other) 
        : m_data(other.m_data)
    { cout << "X copy ctor.\n"; }

    X& operator=(const X& other)
    {
        m_data = other.m_data;       
        cout << "X copy assign.\n";
        return *this;
    }

    int Get() const
    {
        return m_data;
    }

private:
    int m_data;
};

ostream& operator<<(ostream& os, const X& x)
{
    os << x.Get();
    return os;
}

यदि हम for (auto x : v) {...}इस नए वर्ग के साथ उपरोक्त सिंटैक्स का उपयोग करते हैं :

vector<X> v = {1, 3, 5, 7, 9};

cout << "\nElements:\n";
for (auto x : v)
{
    cout << x << ' ';
}

आउटपुट कुछ इस तरह है:

[... copy constructor calls for vector<X> initialization ...]

Elements:
X copy ctor.
1 X copy ctor.
3 X copy ctor.
5 X copy ctor.
7 X copy ctor.
9

जैसा कि इसे आउटपुट से पढ़ा जा सकता है, लूप पुनरावृत्तियों के लिए रेंज-आधारित के दौरान कॉपी कंस्ट्रक्टर कॉल किए जाते हैं।
ऐसा इसलिए है क्योंकि हम कंटेनर के तत्वों को मूल्य ( भाग ) द्वारा कैप्चर कर रहे हैं ।auto xfor (auto x : v)

यह अक्षम कोड है, उदाहरण के लिए, यदि ये तत्व हैं, तो std::stringढेर मेमोरी आवंटन हो सकते हैं, मेमोरी मैनेजर की महंगी यात्राओं के साथ, आदि। यह बेकार है अगर हम सिर्फ एक कंटेनर में तत्वों का निरीक्षण करना चाहते हैं ।

इसलिए, एक बेहतर सिंटैक्स उपलब्ध है: संदर्भ द्वाराconst कैप्चर करें , अर्थात const auto&:

vector<X> v = {1, 3, 5, 7, 9};

cout << "\nElements:\n";
for (const auto& x : v)
{ 
    cout << x << ' ';
}

अब आउटपुट है:

 [... copy constructor calls for vector<X> initialization ...]

Elements:
1 3 5 7 9

बिना किसी स्प्रिचुअल (और संभावित रूप से महंगे) के कंस्ट्रक्टर कॉल को कॉपी करें।

तो, जब अवलोकन एक कंटेनर (यानी, केवल पढ़ने के लिए पहुँच के लिए) में तत्वों, निम्न सिंटैक्स सरल के लिए ठीक है सस्ता करने के लिए प्रतिलिपि प्रकार, जैसे int, double, आदि .:

for (auto elem : container) 

बेकार, constसंदर्भ द्वारा कैप्चर करना सामान्य मामले में बेहतर है , बेकार (और संभावित रूप से महंगी) कॉपी कंस्ट्रक्टर कॉल से बचने के लिए:

for (const auto& elem : container) 

कंटेनर में तत्वों को संशोधित करना

यदि हम श्रेणी-आधारित का उपयोग करके कंटेनर में तत्वों को संशोधित करना चाहते हैं for, तो उपरोक्त for (auto elem : container)और for (const auto& elem : container) वाक्यविन्यास गलत हैं।

वास्तव में, पूर्व मामले में, मूल तत्व की elemएक प्रति संग्रहीत करता है , इसलिए इसमें किए गए संशोधन बस खो जाते हैं और कंटेनर में लगातार संग्रहीत नहीं होते हैं, जैसे:

vector<int> v = {1, 3, 5, 7, 9};
for (auto x : v)  // <-- capture by value (copy)
    x *= 10;      // <-- a local temporary copy ("x") is modified,
                  //     *not* the original vector element.

for (auto x : v)
    cout << x << ' ';

उत्पादन सिर्फ प्रारंभिक अनुक्रम है:

1 3 5 7 9

इसके बजाय, for (const auto& x : v)केवल उपयोग करने का प्रयास संकलित करने में विफल रहता है।

g ++ इस तरह से एक त्रुटि संदेश आउटपुट करता है:

TestRangeFor.cpp:138:11: error: assignment of read-only reference 'x'
          x *= 10;
            ^

इस मामले में सही दृष्टिकोण गैर- constसंदर्भ द्वारा कैप्चरिंग है :

vector<int> v = {1, 3, 5, 7, 9};
for (auto& x : v)
    x *= 10;

for (auto x : v)
    cout << x << ' ';

आउटपुट है (उम्मीद के मुताबिक):

10 30 50 70 90

यह for (auto& elem : container)सिंटैक्स अधिक जटिल प्रकारों के लिए भी काम करता है, उदाहरण के लिए vector<string>:

vector<string> v = {"Bob", "Jeff", "Connie"};

// Modify elements in place: use "auto &"
for (auto& x : v)
    x = "Hi " + x + "!";

// Output elements (*observing* --> use "const auto&")
for (const auto& x : v)
    cout << x << ' ';

आउटपुट है:

Hi Bob! Hi Jeff! Hi Connie!

प्रॉक्सी पुनरावृत्तियों का विशेष मामला

मान लें कि हमारे पास एक है vector<bool>, और हम उपरोक्त सिंटैक्स का उपयोग करते हुए, इसके तत्वों की तार्किक बूलियन स्थिति को पलटना चाहते हैं:

vector<bool> v = {true, false, false, true};
for (auto& x : v)
    x = !x;

उपरोक्त कोड संकलन करने में विफल रहता है।

g ++ इस के समान त्रुटि संदेश देता है:

TestRangeFor.cpp:168:20: error: invalid initialization of non-const reference of
 type 'std::_Bit_reference&' from an rvalue of type 'std::_Bit_iterator::referen
ce {aka std::_Bit_reference}'
     for (auto& x : v)
                    ^

समस्या यह है कि std::vectorटेम्पलेट के लिए विशिष्ट है bool, एक कार्यान्वयन के साथ जो अंतरिक्ष को अनुकूलित करने के लिए एस पैक करता boolहै (प्रत्येक बूलियन मूल्य एक बिट में संग्रहीत होता है, एक बाइट में आठ "बूलियन" बिट्स)।

उसके कारण (चूंकि किसी एक बिट के संदर्भ को वापस करना संभव नहीं है), vector<bool>तथाकथित "प्रॉक्सी इट्रेटर" पैटर्न का उपयोग करता है । एक "प्रॉक्सी इट्रेटर" एक ऐसा इटरेटर है, जो डिरेल्ड होने पर, एक साधारण उपज नहींbool & देता है, बल्कि एक अस्थायी ऑब्जेक्ट को लौटाता है (मान द्वारा) , जो एक प्रॉक्सी क्लास कन्वर्टिबल हैbool । ( स्टैकऑवरफ्लो पर यहां भी देखें यह सवाल और संबंधित जवाब ।)

के तत्वों को संशोधित करने के लिए vector<bool>, एक नए प्रकार के सिंटैक्स (उपयोग auto&&) का उपयोग किया जाना चाहिए:

for (auto&& x : v)
    x = !x;

निम्नलिखित कोड ठीक काम करता है:

vector<bool> v = {true, false, false, true};

// Invert boolean status
for (auto&& x : v)  // <-- note use of "auto&&" for proxy iterators
    x = !x;

// Print new element values
cout << boolalpha;        
for (const auto& x : v)
    cout << x << ' ';

और आउटपुट:

false true true false

ध्यान दें कि for (auto&& elem : container)वाक्यविन्यास साधारण (गैर-छद्म) पुनरावृत्तियों (उदाहरण के लिए vector<int>या a vector<string>) के अन्य मामलों में भी काम करता है ।

(एक साइड नोट के रूप में, उक्त " for (const auto& elem : container)छद्म कार्य " वाक्यविन्यास ठीक छद्म पुनरावृत्त मामले के लिए भी।)

सारांश

उपरोक्त चर्चा को निम्नलिखित दिशानिर्देशों में संक्षेपित किया जा सकता है:

  1. तत्वों के अवलोकन के लिए, निम्नलिखित सिंटैक्स का उपयोग करें:

    for (const auto& elem : container)    // capture by const reference
    • यदि वस्तुओं को कॉपी करना सस्ता है (जैसे ints, doubles, आदि), तो थोड़ा सा सरलीकृत रूप का उपयोग करना संभव है:

      for (auto elem : container)    // capture by value
  2. के लिए संशोधित जगह, उपयोग में तत्वों:

    for (auto& elem : container)    // capture by (non-const) reference
    • यदि कंटेनर "प्रॉक्सी पुनरावृत्तियों" (जैसे std::vector<bool>) का उपयोग करता है, तो उपयोग करें:

      for (auto&& elem : container)    // capture by &&

बेशक, अगर लूप बॉडी के अंदर तत्व की एक स्थानीय प्रतिलिपि बनाने की आवश्यकता है, तो मूल्य द्वारा कैप्चर करना ( for (auto elem : container)) एक अच्छा विकल्प है।


जेनेरिक कोड पर अतिरिक्त नोट्स

में सामान्य कोड , क्योंकि हम सामान्य प्रकार के बारे में मान्यताओं नहीं कर सकते हैं Tप्रतिलिपि के लिए सस्ती जा रहा है, में अवलोकन मोड यह हमेशा उपयोग करने के लिए सुरक्षित है for (const auto& elem : container)
(यह संभावित रूप से महंगी बेकार प्रतियों को ट्रिगर नहीं करेगा, सस्ते-टू-कॉपी प्रकारों के लिए भी ठीक काम करेगा int, और प्रॉक्सी-पुनरावृत्तियों का उपयोग करने वाले कंटेनरों के लिए भी, जैसे std::vector<bool>।)

इसके अलावा, संशोधित मोड में, अगर हम प्रॉक्सी-पुनरावृत्तियों के मामले में भी सामान्य कोड काम करना चाहते हैं , तो सबसे अच्छा विकल्प है for (auto&& elem : container)
(यह साधारण गैर-प्रॉक्सी-पुनरावृत्तियों का उपयोग करने वाले कंटेनरों के लिए भी ठीक काम करेगा, जैसे std::vector<int>या std::vector<string>।)

तो, सामान्य कोड में , निम्नलिखित दिशानिर्देश दिए जा सकते हैं:

  1. तत्वों के अवलोकन के लिए, उपयोग करें:

    for (const auto& elem : container)
  2. के लिए संशोधित जगह, उपयोग में तत्वों:

    for (auto&& elem : container)

7
जेनेरिक संदर्भों के लिए कोई सलाह नहीं? :(
आर। मार्टिनो फर्नांडीस

11
हमेशा उपयोग क्यों नहीं auto&&? वहाँ एक है const auto&&?
मार्टिन बा

1
मुझे लगता है कि आप उस मामले को याद कर रहे हैं जहां आपको वास्तव में लूप के अंदर कॉपी की आवश्यकता है?
जुआनकोपंजा

6
"यदि कंटेनर" प्रॉक्सी पुनरावृत्तियों "का उपयोग करता है" - और आप जानते हैं कि यह "प्रॉक्सी पुनरावृत्तियों" का उपयोग करता है (जो सामान्य कोड में नहीं हो सकता है)। इसलिए मुझे लगता है कि वास्तव में सबसे अच्छा है auto&&, क्योंकि यह auto&समान रूप से अच्छी तरह से कवर करता है ।
क्रिश्चियन राऊ

5
धन्यवाद, कि एक सी / प्रोग्रामर के लिए, के आधार पर सीमा के लिए कुछ महान "क्रैश कोर्स परिचय" था और सीमा के लिए कुछ सुझाव। +1।
एंड्रयूजैकसनजेडए

17

उपयोग करने का कोई सही तरीका नहीं हैfor (auto elem : container) , या for (auto& elem : container)या for (const auto& elem : container)। आप सिर्फ वही व्यक्त करते हैं जो आप चाहते हैं।

मुझे उस पर विस्तार से बताना चाहिए। चलो टहल लेते हैं।

for (auto elem : container) ...

यह एक के लिए कृत्रिम चीनी है:

for(auto it = container.begin(); it != container.end(); ++it) {

    // Observe that this is a copy by value.
    auto elem = *it;

}

आप इसका उपयोग कर सकते हैं यदि इसमें आपके कंटेनर में ऐसे तत्व हैं जो कॉपी करने के लिए सस्ते हैं।

for (auto& elem : container) ...

यह एक के लिए कृत्रिम चीनी है:

for(auto it = container.begin(); it != container.end(); ++it) {

    // Now you're directly modifying the elements
    // because elem is an lvalue reference
    auto& elem = *it;

}

जब आप सीधे कंटेनर में तत्वों को लिखना चाहते हैं, तो इसका उपयोग करें, उदाहरण के लिए।

for (const auto& elem : container) ...

यह एक के लिए कृत्रिम चीनी है:

for(auto it = container.begin(); it != container.end(); ++it) {

    // You just want to read stuff, no modification
    const auto& elem = *it;

}

जैसा कि टिप्पणी कहती है, सिर्फ पढ़ने के लिए। और इसके बारे में, सब कुछ "सही" है जब ठीक से उपयोग किया जाता है।


2
मैंने नमूना कोड के संकलन (लेकिन अकुशल होने), या संकलन करने में असफल होने के कारण, और कुछ समाधानों को प्रस्तावित करने का प्रयास करने के साथ कुछ मार्गदर्शन देने का इरादा किया।
Mr.C64

2
@ Mr.C64 ओह, मुझे क्षमा करें - मैंने अभी देखा है कि यह उन सामान्य प्रश्नों में से एक है। मैं इस साइट पर नया हूं। क्षमा याचना! आपका उत्तर बहुत अच्छा है, मैंने इसे उकेरा - लेकिन यह भी उन लोगों के लिए एक अधिक संक्षिप्त संस्करण प्रदान करना चाहता था जो इसे चाहते हैं । उम्मीद है, मैं घुसपैठ नहीं कर रहा हूं।

1
@ Mr.C64 ओपी को सवाल का जवाब देने में क्या दिक्कत है? यह सिर्फ एक और, वैध, उत्तर है।
mfontanini

1
@mfontanini: अगर कोई मेरे बारे में कुछ बेहतर जवाब दे तो भी कोई समस्या नहीं है। अंतिम उद्देश्य समुदाय को एक विशेष योगदान देना है (विशेषकर शुरुआती लोगों के लिए जो अलग-अलग वाक्यविन्यास और विभिन्न विकल्पों के सामने खोए हुए महसूस कर सकते हैं जो C ++ प्रदान करता है)।
Mr.C64

4

सही साधन हमेशा है

for(auto&& elem : container)

यह सभी शब्दार्थों के संरक्षण की गारंटी देगा।


6
लेकिन क्या होगा यदि कंटेनर केवल परिवर्तनीय संदर्भ देता है और मैं स्पष्ट करना चाहता हूं कि मैं उन्हें लूप में संशोधित नहीं करना चाहता हूं? क्या मुझे auto const &अपना इरादा स्पष्ट करने के लिए उपयोग नहीं करना चाहिए ?
RedX

@RedX: एक "परिवर्तनीय संदर्भ" क्या है?
१०:१ in पर ऑर्बिट

2
@ रीड: संदर्भ कभी नहीं होते हैं const, और वे कभी भी परस्पर नहीं होते हैं। वैसे भी, मेरा जवाब आपके पास है हां, मैं करूंगा
ऑर्बिट

4
जबकि यह काम कर सकता है, मुझे लगता है कि यह श्री सी ६४ के उत्कृष्ट और व्यापक उत्तर द्वारा दिए गए अधिक सुविचारित और विचारशील दृष्टिकोण की तुलना में खराब सलाह है। कम से कम सामान्य भाजक को कम करना C ++ के लिए नहीं है।
जैक ऐडले

6
यह भाषा विकास प्रस्ताव इस "खराब" उत्तर से सहमत है: open-std.org/jtc1/sc22/wg21/docs/papers/2014/n3853.htm
Luc Hermitte

1

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

फॉर-लूप के लिए सिंटैक्टिक आवश्यकता उस range_expressionसमर्थन begin()और end()या तो कार्यों के रूप में है - या तो उस प्रकार के सदस्य कार्यों के रूप में, जो इसका मूल्यांकन करता है या गैर-सदस्य फ़ंक्शन के रूप में जो टाइप का एक उदाहरण लेते हैं।

एक प्रचलित उदाहरण के रूप में, एक संख्या उत्पन्न कर सकता है और निम्न वर्ग का उपयोग करके सीमा पर पुनरावृति कर सकता है।

struct Range
{
   struct Iterator
   {
      Iterator(int v, int s) : val(v), step(s) {}

      int operator*() const
      {
         return val;
      }

      Iterator& operator++()
      {
         val += step;
         return *this;
      }

      bool operator!=(Iterator const& rhs) const
      {
         return (this->val < rhs.val);
      }

      int val;
      int step;
   };

   Range(int l, int h, int s=1) : low(l), high(h), step(s) {}

   Iterator begin() const
   {
      return Iterator(low, step);
   }

   Iterator end() const
   {
      return Iterator(high, 1);
   }

   int low, high, step;
}; 

निम्नलिखित mainफ़ंक्शन के साथ,

#include <iostream>

int main()
{
   Range r1(1, 10);
   for ( auto item : r1 )
   {
      std::cout << item << " ";
   }
   std::cout << std::endl;

   Range r2(1, 20, 2);
   for ( auto item : r2 )
   {
      std::cout << item << " ";
   }
   std::cout << std::endl;

   Range r3(1, 20, 3);
   for ( auto item : r3 )
   {
      std::cout << item << " ";
   }
   std::cout << std::endl;
}

एक निम्नलिखित उत्पादन प्राप्त होगा।

1 2 3 4 5 6 7 8 9 
1 3 5 7 9 11 13 15 17 19 
1 4 7 10 13 16 19 
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.