2012-08-28 11 views
5

एक C++ प्रोग्रामिंग किताब में मैं एक std::list इटरेटर के लिए निम्नलिखित देखा था? क्या अंत में किसी अन्य चर को सहेजना बेहतर होगा या सी ++ कंपाइलर (i। E। G ++) स्वचालित रूप से इसका ख्याल रखेगा?'सूची <T> :: अंत() `अक्षम नहीं है?</p> <pre><code>for (iterator = list.start(); iterator != list.end(); iterator++) </code></pre> <p>नहीं यह अक्षम हर समय कॉल करने के लिए <code>list.end()</code> है:

+0

' std :: list :: end' एक दोगुनी-अंत वाली लिंक्ड सूची का अंत है, इसलिए यह है एक स्थिर समय ऑपरेशन हो सकता है। यह देखते हुए कि आप लूप में रहते समय संलग्न नहीं हो रहे हैं, एक अनुकूल कंपेलर इसे कैश करने में सक्षम होना चाहिए। – oldrinb

+3

यह "समयपूर्व अनुकूलन" के दायरे में होगा। इसके बारे में चिंता करने से जल्द ही समस्याएं पैदा हो सकती हैं, खासकर क्योंकि यदि आप अपने लूप के अंदर सूची में तत्व जोड़ते या हटाते हैं, तो अंत में परिवर्तन करने में सक्षम होता है। यदि आपने अपने कोड का एक विशिष्ट हिस्सा अलग किया है (जो इस पर निर्भर करता है) एक सीपीयू हॉग होने के नाते, तो हर तरह से अनुकूलित करें, लेकिन अन्यथा समुद्र में बड़ी मछली है। – Wug

+0

@Wug अगर आपको पता है कि '.end() 'निरंतर समय है, हाँ (और हाँ, मुझे पता है कि इस विशिष्ट कंटेनर के लिए यह C++ में होना आवश्यक है)। लेकिन सूचियों के लिए जो बहुत लंबा हो सकता है और जिसका '.end() 'ओ (एन) है, ऐसा अनुकूलन बिल्कुल समय से पहले नहीं है। – delnan

उत्तर

4

std::list<T>::end() पर कॉल एक बड़ी दक्षता समस्या होने की संभावना नहीं है और शायद केवल एक ही मूल्य पढ़ता है। हालांकि, आप संकलक को संकेत देते हैं कि यह एक चर को संग्रहीत करके बदलने के लिए नहीं है। अन्य कंटेनरों के लिए एक आधार पता पढ़ने के अलावा एक गणना शामिल हो सकती है जो थोड़ा अधिक शामिल है। अभी भी नाटकीय लेकिन संभवतः टालने के लायक कुछ भी नहीं।

नोट, हालांकि, यह लूप का अर्थपूर्ण भी बदल सकता है: यदि लूप का शरीर तत्वों को जोड़ सकता है, तो पूर्व अंत स्थानांतरित हो सकता है। दिलचस्प बात यह है कि मुझे मानक में कोई विशिष्ट आवश्यकता नहीं है कि कंटेनर में तत्व डालने पर std::list<T>::end() बदल सकता है (मैं उन क्रियाओं की कल्पना कर सकता हूं जहां यह बदलता है और साथ ही कुछ जहां यह नहीं करता है; अधिकतर संभावना यह नहीं बदलेगी , हालांकि)। यदि आप सूची को संशोधित करते समय गारंटीकृत व्यवहार प्राप्त करना चाहते हैं, तो आप प्रत्येक पुनरावृत्ति में list.end() पर बहुत अच्छी तरह से कॉल कर सकते हैं।

बीटीडब्ल्यू, ++iterator के बजाय iterator++ का उपयोग करने के बारे में एक बड़ी प्रदर्शन चिंता है, विशेष रूप से यह वास्तव में लेखक में पुस्तक में उपयोग किया जाता है। फिर भी, यह एक माइक्रो ऑप्टिमाइज़ेशन है जैसे list.end() के परिणाम को संग्रहित करना, लेकिन एक सस्ता करना।

+0

"मुझे यह बताते हुए मानक में कोई विशिष्ट आवश्यकता नहीं है कि क्या std :: list :: कंटेनर में तत्व डालने पर अंत()' बदल सकता है "- निश्चित रूप से 23.3.5.4/1," वैधता को प्रभावित नहीं करता है इटरेटर और संदर्भों के "? चूंकि यह अभी भी मान्य है, इसे अभी भी कहीं भी संदर्भित करना चाहिए, और चूंकि आप अंत पुनरावर्तक के बाद सम्मिलित नहीं कर सकते हैं, यह अभी भी वही स्थान होना चाहिए। –

+0

क्या अंत में एक तत्व डालने पर नए मूल्य के साथ शुरू किया गया है जो अर्ध-बेक्ड ऑब्जेक्ट को 'अंत()' बिंदु रखने के लिए कार्यान्वयन पर प्रतिबंध लगाता है? इटेटरेटर वैध रहता है लेकिन अब अंत तक इंगित नहीं करता है और इससे पहले कि कोई संदर्भ या सूचक अमान्य हो जाए, इस स्थान में कोई ऑब्जेक्ट नहीं था। जैसा कि होता है, इस प्रकार 'std :: vector ' पर्याप्त क्षमता होने पर व्यवहार करता है। मैं मानता हूं कि यह एक अजीब कार्यान्वयन होगा लेकिन डीएस 9 के कार्यान्वयन को जानबूझकर सबसे अजीब कार्यान्वयन चुनने के लिए जाना जाता है (यह अप्रत्याशित परिस्थितियों में एक तरह से व्यवहार में भी व्यवहार कर सकता है)। –

+0

उचित बिंदु। ब्याज से बाहर, अगर मेरे पास एक पुनरावर्तक है कि * एक अंतिम पुनरावर्तक नहीं है, और मैं इससे पहले सम्मिलित हूं, तो क्या कहता है कि बाद में यह नव-सम्मिलित वस्तु को इंगित नहीं करता है? यहां तक ​​कि पर्याप्त क्षमता के साथ, 'वेक्टर :: डालने' प्रविष्टि बिंदु के बाद इटरेटर और संदर्भों को अमान्य कर देता है, इसलिए पुरानी अंत इटरेटर के लिए अभी भी एक अंत इटरेटर के लिए गैर-आवश्यकता स्पष्ट है। –

8

list::end() में निरंतर समय जटिलता होनी चाहिए और विशेष रूप से लिंक्ड सूचियों के लिए इसका मतलब है कि यह शायद बहुत ही कुशल है।

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

ओह, और स्टीव जेसॉप के स्वयं को दक्षता का परीक्षण करने के उत्तर को पढ़ें!

+0

http://www.cplusplus.com/reference/stl/list/end/ - मैं वहां सामान की जांच करता हूं, "जटिलता" देखें। – Shi

+0

@hihi, ध्यान दें कि "निरंतर" जटिलता का कोई मतलब नहीं है। विशेष रूप से वेक्टर अपने संबंधित 'अंत() 'के भीतर सूचक जोड़ करते हैं; सूची शायद नहीं (जैसा कि मैंने उल्लेख किया है) –

+0

लगातार जटिलता में कुछ समय लगता है, और आपके एसटीएल, कार्यान्वयन, चुने हुए कंटेनर आदि (विशेष रूप से गैर-एसटीएल कंटेनर) के आधार पर, यह काफी समय हो सकता है। – ssube

2

अभ्यास में, एसटीएल कंटेनर के लिए, container::end() बेहद सस्ता है। वास्तव में, सी ++ मानक कई वर्गों (यदि सभी के लिए नहीं) के कई तरीकों के लिए एल्गोरिदमिक जटिलता अनिवार्य है, और container::end() हमेशा स्थिर समय होता है।

इसके अलावा, कंपाइलर उन विधियों को इनलाइन करने के लिए स्वतंत्र है, जो अनिवार्य रूप से किसी भी ओवरहेड को हटा सकता है। मैं इसे संग्रहीत करने के बजाय निरंतर समय में किसी सूची का अंत प्राप्त करने का कोई अन्य तरीका नहीं सोच सकता, इसलिए आपकी list.end() कॉल शायद फ़ील्ड एक्सेस होने के समाप्त हो जाती है, जो इसे स्टैक पर संग्रहीत करने से x86 प्लेटफॉर्म पर अधिक महंगा नहीं है।

आपका माइलेज अन्य संग्रहों के साथ भिन्न हो सकता है, लेकिन यह एक सुरक्षित शर्त है कि list.end() आपकी बाधा बनने के समाप्त नहीं होगा।

4

कोई फर्क नहीं पड़ता है।

मानक कंटेनर फ़ंक्शंस इनलाइन हो जाते हैं, इसलिए कोई ध्यान देने योग्य फ़ंक्शन कॉल ओवरहेड नहीं होना चाहिए। क्या बचा है कि क्या ऑप्टिमाइज़र अनावश्यक ओवरहेड से बचने के लिए पर्याप्त स्मार्ट है जो तुलना करने के लिए कड़ाई से जरूरी नहीं है। उदाहरण के लिए: क्या यह वास्तव में एक अस्थायी list::iterator ऑब्जेक्ट बनाता है, अपने वर्तमान स्थिति फ़ील्ड को भरें, और उसके बाद उस फ़ील्ड को वापस पढ़ें, या तुलना iterator से मान के बीच सूचक सूचक के रूप में समाप्त होती है और उसके सिर में एक मान सूची?

यहां तक ​​कि अगर कुछ अनावश्यक ओवरहेड है, तो यह इटेटरेटर को बढ़ाने और आपके लूप बॉडी के मुकाबले और भी नगण्य होने की तुलना में नगण्य हो सकता है।

आप इसका परीक्षण कर सकते हैं, जो अनुमान लगाने से अधिक विश्वसनीय है। ऑप्टिमाइज़ेशन सक्षम करने के लिए याद रखें - ऑप्टिमाइज़ेशन के बिना परीक्षण प्रदर्शन ऐसा कहने जैसा है कि ब्लेक बोल्ट से तेज होना चाहिए यदि ब्लेक गर्म-गरम ट्रैक से बस तक चलता है।

4

आम तौर पर, नहीं, यह अक्षम नहीं है। end() आमतौर पर एक इनलाइन फ़ंक्शन होगा, और संकलक जो भी करता है उसे करने के लिए अच्छा कोड उत्पन्न करेगा। बिंदु के लिए अधिक, क्या तुलना में अक्षम? हां, आप परिणाम को पकड़ने के लिए एक चर बनाने के लिए कोड जोड़ सकते हैं, और यह शायद end() पर कॉल करने से थोड़ा तेज़ हो सकता है या नहीं। ऐसा लगता है कि इस तरह के बदलाव से एक कार्यक्रम को चालू करने के लिए पर्याप्त गति अंतर आएगा जो आवश्यकताओं को पूरा करने वाले व्यक्ति में बहुत धीमा है।

1

यदि आपको माइक्रो-ऑप्टिमाइज़ करने की आवश्यकता है, तो हाँ।

सामान्यतः, list.end() पर कॉल करने से महत्वपूर्ण प्रदर्शन दंड नहीं होगा और शायद कोई समस्या नहीं होगी। यह प्रत्येक कॉल के समान मूल्य वापस कर सकता है, इनलाइन किया जा सकता है, और इसी तरह। जबकि धीमी नहीं है, इसमें कुछ समय लगता है।

यदि आपको बिल्कुल गति की आवश्यकता है, तो आप for (iterator = list.start(), end = list.end; iteration != end; ++iterator) का उपयोग करना चाहते हैं। यह अंत इटरेटर (और प्री-इंक करता है) को कैश करता है, और इसमें दोहराई गई कॉल नहीं होनी चाहिए।

दूसरा प्रकार आम तौर पर अनावश्यक है, लेकिन यदि .end() महंगा है या लूप बहुत बड़ा है, तो उपयोगी हो सकता है।

1

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

for (mylist::iterator it = alist.begin(), finish = alist.end(); it != finish; ++it) 

संकलक अगर यह कर सकते हैं आप के लिए इस अनुकूलन बनाने के लिए की संभावना नहीं है ' टी निर्धारित नहीं है कि कंटेनर बदल नहीं रहा है।

ध्यान दें कि यह एक मापनीय समय अंतर बनाने की संभावना नहीं है, लेकिन यह चोट नहीं पहुंचा सकता है।

+0

यदि कोड किसी ऐसे सिस्टम पर चल रहा है जो रजिस्टरों के लिए कसकर है या सीमित स्टैक है, तो अतिरिक्त चर बनाने से चोट लग सकती है। –

+0

@PeteBecker, क्योंकि चर एक फ़ंक्शन कॉल के परिणाम को बदल रहा है, मुझे लगता है कि यह चोट पहुंचाने की बजाय मदद करने की संभावना है। हालांकि आप अभी भी सही हो सकते हैं, जेनरेट कोड को देखे बिना जानना असंभव है। –

+0

सिवाय इसके कि मूल्य लूप बॉडी के माध्यम से चारों ओर रखा जाना चाहिए; फ़ंक्शन कॉल के साथ परिणाम का उपयोग करने के बाद इसे त्याग दिया जा सकता है। –

0

एक अच्छे कारण के कैश नहीं करने के लिए end()std::list पर है कि यह निम्नलिखित गलती कर से रोकता है:

for (iterator = list.rstart(), end = list.rend(); iterator != end; iterator++) { 
    // modify list 

नहीं iterators अवैध जाएगा जब आप std::list में संशोधन करने के लिए, लेकिन rend एक प्रहरी नहीं है (यह अंतर्निहित सूची के पहले तत्व को इंगित करता है), जिसका अर्थ यह है कि यदि आप रिवर्स सूची (उर्फ अपरिवर्तित सूची की शुरुआत में प्रीपेड करते हैं) के अंत में संलग्न होते हैं तो यह सूची का अंत होने से रोक देगा।