13

मैं एक प्रश्न के साथ भ्रमित हूं जिसे मैंने सी ++ परीक्षण में देखा था। कोड यहाँ है:एक अधिभारित ऑपरेटर को समझना [] उदाहरण

#include <iostream> 
using namespace std; 

class Int { 
public: 
    int v; 
    Int(int a) { v = a; } 
    Int &operator[](int x) { 
     v+=x; 
     return *this; 
    } 
}; 
ostream &operator<< (ostream &o, Int &a) { 
    return o << a.v; 
} 

int main() { 
    Int i = 2; 
    cout << i[0] << i[2]; //why does it print 44 ? 
    return 0; 
} 

मैं थोड़े यकीन है कि यह 24 मुद्रित होता था लेकिन इसके बजाय यह 44 प्रिंट करता है। मैं वास्तव में किसी को यह स्पष्ट करने के लिए पसंद करूंगा। क्या यह एक संचयी मूल्यांकन है? <<बाइनरी इंफिक्स भी है?

अग्रिम

संपादित में धन्यवाद: नहीं अच्छी तरह से परिभाषित ऑपरेटर अधिभार के मामले में, किसी अतिभारित ऑपरेटरों के बेहतर कार्यान्वयन यहाँ तो यह 24 मुद्रित होगा दे सकता है?

+1

'cout << i [0] << i [2]; 'इस तरह लिखा जा सकता है:' ऑपरेटर << (ऑपरेटर << (cout, i [0]), i [2]);' – Xaqq

+1

ऐसा लगता है कि दो के मूल्यांकन का क्रम << अच्छी तरह परिभाषित नहीं है। अपना कोड डीबग करें और आप देखेंगे कि मैं [2] पहले कहलाता हूं। – Tim3880

+0

मैं वास्तव में 'ऑपरेटर []' दुष्प्रभाव बनाने के खिलाफ सुझाव दूंगा। मुझे लगता है कि व्यवहार आपको किसी बिंदु पर फिर से काटने जा रहा है न कि यहां। कल्पना करें कि यह मल्टीथ्रेडिंग के साथ कैसे इंटरैक्ट करता है, उदा। –

उत्तर

16

इस कार्यक्रम के अनिश्चित व्यवहार है: संकलक एक बाएँ से सही क्रम में i[0] और i[2] का मूल्यांकन करने की आवश्यकता नहीं है (C++ भाषा अनुकूलन के लिए अनुमति देने के लिए आदेश में compilers को यह स्वतंत्रता देता है)।

उदाहरण के लिए, Clang does it in this instance, जबकि GCC does not

मूल्यांकन का क्रम निर्दिष्ट नहीं है, इसलिए आप एक सतत आउटपुट की अपेक्षा नहीं कर सकते हैं, न कि एक विशिष्ट मशीन पर बार-बार अपना प्रोग्राम चलाते समय भी।

आप लगातार उत्पादन प्राप्त करना चाहता था, तो आप इसके बाद के संस्करण के रूप में इस प्रकार है (या कुछ बराबर तरीके से) अलग तरीके से व्यक्त कर सकते हैं:

cout << i[0]; 
cout << i[2]; 

आप देख सकते हैं, जीसीसी इस मामले में no longer outputs 44

संपादित करें:

, तो जो भी कारण के लिए, तुम सच में वास्तव में अभिव्यक्ति cout << i[0] << i[2]24 प्रिंट करना चाहते हैं, तो आप, अतिभारित ऑपरेटरों (operator [] और operator <<) काफी की परिभाषा को संशोधित करना होगा क्योंकि भाषा जानबूझकर यह कहना असंभव बनाता है कि कौन सा सबएक्सप्रेस (i[0] या [i2]) पहले मूल्यांकन किया जाता है।

केवल गारंटी यहां पहुंचने है कि i[0] के मूल्यांकन का परिणाम है, तो आपका सर्वश्रेष्ठ दांव operator <<Int's डेटा सदस्य v के संशोधन निष्पादित करने देने के शायद है i[2] के मूल्यांकन का परिणाम से पहले cout में डाला जा रहा है, बल्कि operator [] से अधिक।

हालांकि, डेल्टा कि v को लागू किया जाना चाहिए operator [] लिए एक तर्क के रूप पारित हो जाता है, और आप operator << करने के लिए इसे एक साथ अग्रेषण मूल Int वस्तु के साथ की किसी तरह की जरूरत है।,

class Int; 

struct Decorator { 
    Decorator(Int& i, int v) : i{i}, v{v} { } 
    Int& i; 
    int v; 
}; 

class Int { 
public: 
    int v; 
    Int(int a) { v = a; } 
    Decorator operator[](int x) { 
     return {*this, x}; // This is C++11, equivalent to Decorator(*this, x) 
    } 
}; 

अब आप सिर्फ इसलिए operator << के पुनर्लेखन के लिए है कि यह एक Decorator वस्तु को स्वीकार करता है की जरूरत है संदर्भित Int को संशोधित करता है: एक संभावना यह operator [] वापसी कि डेल्टा शामिल एक डेटा संरचना के साथ ही मूल वस्तु के लिए एक संदर्भ देना है

ostream &operator<< (ostream &o, Decorator const& d) { 
    d.i.v += d.v; 
    o << d.i.v; 
    return o; 
} 

यहाँ एक live example है: v डेटा सदस्य को संग्रहीत डेल्टा लगाने से वस्तु है, तो अपने मूल्य प्रिंट करता है।

अस्वीकरण: दूसरों के रूप में उल्लेख किया है, यह ध्यान रखें operator [] और operator << हैं कि आम तौर पर गैर-परिवर्तनशील परिचालन (अधिक सटीक, वे वस्तु जो अनुक्रमणित है और धारा-डाला, क्रमशः के राज्य उत्परिवर्तित नहीं है), इसलिए आप इस तरह के कोड लिखने से बहुत निराश हैं जबतक कि आप कुछ सी ++ ट्रिविया को हल करने की कोशिश नहीं कर रहे हैं।

+4

@vsoftco: व्यवहार इस मामले में अनिश्चित नहीं है, केवल अनिश्चित है। अधिभारित करने के लिए कॉल 'ऑपरेटर []' केवल नियमित फ़ंक्शन कॉल हैं और वे इंटरलीव नहीं कर सकते हैं (अनुच्छेद 1.9/15 देखें) –

+1

हम्म, लेकिन पैरा। इसके साथ समाप्त होता है: * यदि स्केलर ऑब्जेक्ट पर एक साइड इफेक्ट एक ही स्केलर ऑब्जेक्ट या किसी अन्य स्केलर ऑब्जेक्ट के मान का उपयोग करके मूल्य गणना के किसी अन्य दुष्प्रभाव के सापेक्ष अपरिचित है, और वे संभावित रूप से समवर्ती (1.10) नहीं हैं, व्यवहार अपरिभाषित है। * मैं थोड़ी उलझन में हूं, 'ऑपरेटर []' ए "साइड इफेक्ट नहीं है? ओह ठीक है, उदाहरण के बाद मैं हिस्सा चूक गया। धन्यवाद! – vsoftco

+1

@vsoftco: दाएं, फुटनोट 9 भी थोड़ा सा स्पष्टीकरण देता है: "* दूसरे शब्दों में, फ़ंक्शन निष्पादन एक-दूसरे के साथ अंतःस्थापित नहीं होते हैं।" –

5

यह बताने के लिए कि यहां क्या हो रहा है, चलिए चीजों को अधिक सरल बनाते हैं: cout<<2*2+1*1;। पहले क्या होता है, 2 * 2 या 1 * 1? एक संभावित उत्तर 2 * 2 पहले होना चाहिए, क्योंकि यह सबसे बाएं चीज है। लेकिन सी ++ मानक कहता है: कौन परवाह करता है ?! आखिरकार, परिणाम 5 या तो रास्ता है। लेकिन कभी-कभी यह मायने रखता है। उदाहरण के लिए, यदि f और g दो कार्य हैं, और हम f()+g() करते हैं, तो कोई गारंटी नहीं है जिसे पहले कहा जाता है। यदि f कोई संदेश प्रिंट करता है, लेकिन g प्रोग्राम से बाहर निकलता है, तो संदेश कभी मुद्रित नहीं किया जा सकता है। आपके मामले में, i[2] को i[0] से पहले बुलाया गया, क्योंकि सी ++ सोचता है कि इससे कोई फर्क नहीं पड़ता। आपके पास दो विकल्प हैं:

एक विकल्प आपके कोड को बदलना है, इससे कोई फर्क नहीं पड़ता। अपने [] ऑपरेटर को फिर से लिखें ताकि यह Int को परिवर्तित न करे, और इसके बजाय एक नया Int लौटाता है। यह शायद वैसे भी एक अच्छा विचार है, क्योंकि यह ग्रह पर अन्य सभी [] ऑपरेटरों के 99% के साथ संगत बनाएगा। इसके लिए कम कोड की आवश्यकता होती है:

Int &operator[](int x) { return this->v + x;}। 2 * 2 पहले किया जाता है,

cout<<i[0]; cout<<i[2];

कुछ भाषाओं में वास्तव में गारंटी देते हैं कि 2*2+1*1 में कार्य करें:

आपका दूसरा विकल्प आपके [] एक ही रखने के लिए है, और दो कथनों में अपने मुद्रण अलग हो गए। लेकिन सी ++ नहीं।

संपादित करें: मैं जितना स्पष्ट था उतना स्पष्ट नहीं था। आइए धीरे-धीरे कोशिश करें। 2*2+1*1 का मूल्यांकन करने के लिए सी ++ के दो तरीके हैं।

विधि 1: 2*2+1*1 ---> 4+1*1 ---> 4+1 --->5

विधि 2: 2*2+1*1 ---> 2*2+1 ---> 4+1 --->5

दोनों मामलों में हमें एक ही जवाब मिलता है।

आइए इसे फिर से एक अलग अभिव्यक्ति के साथ आज़माएं: i[0]+i[2]

विधि 1: i[0]+i[2] ---> 2+i[2] ---> 2+4 ---> 6

विधि 2: i[0]+i[2] ---> i[0]+4 ---> 4+4 ---> 8

हमें एक अलग उत्तर मिला, क्योंकि [] के दुष्प्रभाव हैं, इसलिए यह महत्वपूर्ण है कि हम i[0] या i[2] पहले करें। सी ++ के अनुसार, ये दोनों वैध उत्तर हैं। अब, हम आपकी मूल समस्या पर हमला करने के लिए तैयार हैं। जैसा कि आप जल्द ही देखेंगे, इसमें << ऑपरेटर के साथ लगभग कुछ भी नहीं है।

सी ++ cout << i[0] << i[2] के साथ कैसे काम करता है? पहले की तरह, दो विकल्प हैं।

विधि 1: cout << i[0] << i[2] ---> cout << 2 << i[2] ---> cout << 2 << 4

विधि 2: cout << i[0] << i[2] ---> cout << i[0] << 4 ---> cout << 4 << 4

पहली विधि आपके जैसा अपेक्षित 24 प्रिंट करेगी। लेकिन सी ++ के अनुसार, विधि 2 समान रूप से अच्छा है, और यह आपके जैसा 44 प्रिंट करेगा। ध्यान दें कि समस्या << कहने से पहले होती है। इसे रोकने के लिए << अधिभारित करने का कोई तरीका नहीं है, क्योंकि << समय चल रहा है, "क्षति" पहले ही हो चुकी है।

+0

+1 यह उल्लेख करने के लिए कि यहां [] विधि कितनी बेवकूफ है (और अन्य सामान भी बताए गए हैं!) क्योंकि ... मैंने पहले कभी इसका उपयोग नहीं देखा है! –

+0

@ मार्कवी हैलो मार्क। आपने कहां पढ़ा कि ऑपरेटरों में सी ++ की प्राथमिकता नहीं है? '*' ऑपरेटर की '+' से अधिक प्राथमिकता है। किसी भी मामले में, इस कोड नमूने में समस्या सबस्क्रिप्ट ऑपरेटर '[]' इम्प्लांटेशन में नहीं है लेकिन '<< 'में है। – BugShotGG

+0

@GeoPapas मुझे लगता है कि आप इस उत्तर को गलत तरीके से पढ़ते हैं। ऑपरेटर में सी ++ की प्राथमिकता है और '*' '' 'की तुलना में अधिक प्राथमिकता है, यह उत्तर आपको पहले से ही जानता है। शायद आप मूल्यांकन के क्रम में प्राथमिकता को जोड़ रहे हैं। –

संबंधित मुद्दे