c++ - एक डिफ़ॉल्ट डिफ़ॉल्ट को प्रतिस्थापित क्यों करेगा और ऑपरेटरों को हटा देगा?
operator-overloading new-operator (5)
डिफ़ॉल्ट डिफॉल्ट ऑपरेटर को new
क्यों बदलना चाहिए और एक कस्टम new
साथ delete
चाहिए और ऑपरेटरों को delete
देना चाहिए ?
यह नए अधिभार की निरंतरता में है और अत्यधिक रोशनी C ++ FAQ में हटाएं :
ऑपरेटर ओवरलोडिंग।
इस FAQ में एक फॉलोअप प्रविष्टि है:
मुझे आईएसओ सी ++ मानक अनुरूप अनुरूप कस्टम कैसे लिखना चाहिए और ऑपरेटरों को delete
देना चाहिए?
नोट: उत्तर स्कॉट मेयर्स के अधिक प्रभावी सी ++ के पाठों पर आधारित है।
(नोट: यह स्टैक ओवरफ्लो के सी ++ एफएक्यू में प्रवेश करने के लिए है । यदि आप इस फॉर्म में एक एफएक्यू प्रदान करने के विचार की आलोचना करना चाहते हैं, तो मेटा पर पोस्ट करना जो यह सब शुरू कर देगा, ऐसा करने का स्थान होगा। उस प्रश्न की निगरानी सी ++ चैटरूम में की जाती है, जहां एफएक्यू विचार पहली जगह शुरू हुआ था, इसलिए आपके उत्तर को उन लोगों द्वारा पढ़ा जाने की संभावना है जो इस विचार के साथ आए थे।)
ऑपरेटर नया है कि कुछ कंपाइलर्स के साथ जहाज डबल्स के गतिशील आवंटन के लिए आठ बाइट संरेखण की गारंटी नहीं देता है।
उद्धरण, कृपया। आमतौर पर, डिफ़ॉल्ट नया ऑपरेटर मॉलोक रैपर की तुलना में केवल थोड़ा अधिक जटिल होता है, जो मानक द्वारा, उस स्मृति को लौटाता है जो लक्ष्य आर्किटेक्चर का समर्थन करता है किसी भी डेटाटाइप के लिए उपयुक्त रूप से गठबंधन किया जाता है।
ऐसा नहीं है कि मैं कह रहा हूं कि नए अधिभारित करने और अपने स्वयं के वर्गों के लिए हटाने के अच्छे कारण नहीं हैं ... और आपने यहां कई वैध लोगों को छुआ है, लेकिन उपर्युक्त उनमें से एक नहीं है।
कई कंप्यूटर आर्किटेक्चर की आवश्यकता होती है कि विशेष प्रकार के डेटा को विशेष प्रकार के पते पर स्मृति में रखा जाए। उदाहरण के लिए, एक आर्किटेक्चर की आवश्यकता हो सकती है कि पॉइंटर्स उन पतों पर होते हैं जो चार में से एक हैं (यानी, चार-बाइट गठबंधन हो) या यह कि आठों में से एक के बराबर पते पर युगल होना चाहिए (यानी आठ-बाइट गठबंधन हो)। ऐसी बाधाओं का पालन करने में विफलता रन-टाइम पर हार्डवेयर अपवादों का कारण बन सकती है। अन्य आर्किटेक्चर अधिक क्षमाशील हैं, और प्रदर्शन को कम करने के बावजूद इसे काम करने की अनुमति दे सकते हैं।
स्पष्टीकरण के लिए: यदि किसी आर्किटेक्चर को उदाहरण के लिए जरूरी है कि double
डेटा आठ-बाइट गठबंधन हो, तो अनुकूलित करने के लिए कुछ भी नहीं है। उपयुक्त आकार के किसी प्रकार का गतिशील आवंटन (जैसे malloc(size)
, operator new(size)
, operator new[](size)
, new char[size]
जहां size >= sizeof(double)
) को ठीक से गठबंधन करने की गारंटी है । यदि कोई कार्यान्वयन इस गारंटी को नहीं बनाता है, तो यह अनुरूप नहीं है। उस मामले में 'सही चीज़' करने के लिए operator new
बदलना कार्यान्वयन नहीं, कार्यान्वयन को 'फिक्सिंग' करने का प्रयास होगा।
दूसरी तरफ, कुछ आर्किटेक्चर एक या अधिक डेटा प्रकारों के लिए अलग-अलग (या सभी) संरेखण की अनुमति देते हैं, लेकिन उन समान प्रकारों के संरेखण के आधार पर विभिन्न प्रदर्शन गारंटी प्रदान करते हैं। एक कार्यान्वयन फिर स्मृति को वापस कर सकता है (फिर से, उपयुक्त आकार का अनुरोध मानता है) जो उप-अनुकूल रूप से गठबंधन है, और फिर भी अनुरूप है। उदाहरण के बारे में यही है।
ऐसा लगता है कि "वैश्विक नए अधिभारित करने और हटाने के किसी भी कारण से" मेरे उत्तर से सूची को दोहराने लायक है ? यहां - अधिक विस्तृत चर्चा, संदर्भ, और अन्य कारणों के लिए उस उत्तर (या वास्तव में उस प्रश्न के अन्य उत्तरों ) देखें। ये कारण आम तौर पर स्थानीय ऑपरेटर ओवरलोड के साथ-साथ डिफ़ॉल्ट / वैश्विक वाले, और सी calloc
/ calloc
/ calloc
/ free
ओवरलोड या हुक पर भी लागू होते हैं।
हम वैश्विक नए अधिभार और ऑपरेटरों को हटाते हैं जहां मैं कई कारणों से काम करता हूं:
- सभी छोटे आवंटन को पूल करना - ओवरहेड घटता है, विखंडन घटता है, छोटे-आवंटित-भारी ऐप्स के लिए प्रदर्शन बढ़ा सकता है
- ज्ञात जीवनकाल के साथ आवंटन तैयार करना - इस अवधि के अंत तक सभी फ्री को अनदेखा करें, फिर उन सभी को एक साथ मुक्त करें (माना जाता है कि हम वैश्विक ऑपरेटर के मुकाबले स्थानीय ऑपरेटर ओवरलोड के साथ ऐसा करते हैं)
- संरेखण समायोजन - कैशलाइन सीमाओं, आदि के लिए
- alloc fill - uninitialized चर के उपयोग का पर्दाफाश करने में मदद
- मुफ्त भरें - पहले हटाए गए स्मृति के उपयोग को बेनकाब करने में मदद करें
- देरी मुक्त - मुक्त भरने की प्रभावशीलता में वृद्धि, कभी-कभी प्रदर्शन में वृद्धि
- सेंटीनेल या बाड़पोस्ट - बफर ओवररन्स, अंडरन्स, और कभी-कभी जंगली सूचक का पर्दाफाश करने में मदद करते हैं
- आवंटन को पुनर्निर्देशित करना - NUMA, विशेष मेमोरी क्षेत्रों, या यहां तक कि स्मृति में अलग सिस्टम को अलग रखने के लिए (उदाहरण के लिए एम्बेडेड स्क्रिप्टिंग भाषा या डीएसएल)
- कचरा संग्रह या सफाई - उन एम्बेडेड स्क्रिप्टिंग भाषाओं के लिए फिर से उपयोगी
- ढेर सत्यापन - आप यह सुनिश्चित करने के लिए हर चीज ठीक दिखने के लिए हर एन ऑलॉक्स / फ्री के ढेर डेटा संरचना के माध्यम से चल सकते हैं
- लेखांकन , रिसाव ट्रैकिंग और उपयोग स्नैपशॉट / आंकड़े (ढेर, आवंटन आयु, आदि) सहित
कोई new
बदलने और ऑपरेटरों को कई कारणों से delete
प्रयास कर सकता है, अर्थात्:
उपयोग त्रुटियों का पता लगाने के लिए:
ऐसे कई तरीके हैं जिनमें new
और delete
गलत उपयोग से अपरिभाषित व्यवहार और मेमोरी लीक के डरावने जानवरों का कारण बन सकता है। प्रत्येक के प्रासंगिक उदाहरण हैं:
new
एड मेमोरी पर एक से अधिक delete
का उपयोग करना और new
का उपयोग करके आवंटित स्मृति पर delete
नहीं करना।
एक ओवरलोडेड ऑपरेटर new
आवंटित पतों की सूची रख सकता है और ओवरलोडेड ऑपरेटर delete
सूची से पते को delete
सकता है, तो ऐसी उपयोग त्रुटियों का पता लगाना आसान है।
इसी तरह, प्रोग्रामिंग गलतियों की एक किस्म डेटा ओवररन्स (आवंटित ब्लॉक के अंत से परे लिखना) और अंडर्रन्स (आवंटित ब्लॉक की शुरुआत से पहले लिखना) का कारण बन सकती है।
एक अधिभारित ऑपरेटर new
ब्लॉक को आवंटित कर सकता है और ग्राहकों को उपलब्ध स्मृति के पहले और बाद में ज्ञात बाइट पैटर्न ("हस्ताक्षर") डाल सकता है। ओवरलोडेड ऑपरेटर डिलीट यह देखने के लिए जांच सकते हैं कि हस्ताक्षर अभी भी बरकरार हैं या नहीं। इस प्रकार यह जांच करके कि क्या ये हस्ताक्षर बरकरार नहीं हैं, यह निर्धारित करना संभव है कि आवंटित ब्लॉक के जीवन के दौरान कभी-कभी ओवररन या अंडर-रन हुआ, और ऑपरेटर डिलीट उस तथ्य को लॉग कर सकता है, अपमानजनक सूचक के मूल्य के साथ, इस प्रकार सहायता एक अच्छी नैदानिक जानकारी प्रदान करने में।
दक्षता में सुधार (गति और स्मृति):
new
और delete
ऑपरेटर सभी के लिए उचित रूप से अच्छी तरह से काम करते हैं, लेकिन किसी के लिए सबसे अच्छा नहीं। यह व्यवहार इस तथ्य से उत्पन्न होता है कि वे केवल सामान्य उद्देश्य के उपयोग के लिए डिज़ाइन किए गए हैं। उन्हें आवंटन पैटर्न को समायोजित करना होगा जो कार्यक्रम की अवधि के लिए मौजूद कुछ ब्लॉक के गतिशील आवंटन से निरंतर आवंटन और बड़ी संख्या में अल्पकालिक वस्तुओं की निरस्तीकरण के लिए हैं। आखिरकार, ऑपरेटर new
और ऑपरेटर उस जहाज को कंपाइलर्स के साथ delete
जो मध्य-सड़क की रणनीति लेता है।
यदि आपको अपने प्रोग्राम के डायनामिक मेमोरी उपयोग पैटर्न की अच्छी समझ है, तो आप अक्सर ऑपरेटर के कस्टम संस्करण और ऑपरेटर को निष्पादित कर सकते हैं (प्रदर्शन में तेज, या 50% तक कम मेमोरी की आवश्यकता होती है) डिफ़ॉल्ट रूप से। बेशक, जब तक कि आप यह सुनिश्चित न करें कि आप क्या कर रहे हैं, ऐसा करने का अच्छा विचार नहीं है (अगर आप शामिल जटिलताओं को समझ नहीं पाते हैं तो इसे भी न करें)।
उपयोग आंकड़े एकत्र करने के लिए:
# 2 में उल्लिखित दक्षता में सुधार के लिए new
को delete
और delete
बारे में सोचने से पहले, आपको जानकारी एकत्र करनी चाहिए कि आपका एप्लिकेशन / प्रोग्राम गतिशील आवंटन का उपयोग कैसे करता है। आप इसके बारे में जानकारी एकत्र करना चाह सकते हैं:
आवंटन ब्लॉक का वितरण,
जीवनकाल का वितरण,
आवंटन का आदेश (फीफो या लिफो या यादृच्छिक)
उपयोग पैटर्न को समझना समय की अवधि में बदलता है, गतिशील स्मृति की अधिकतम मात्रा आदि का उपयोग किया जाता है।
साथ ही, कभी-कभी आपको उपयोग की जानकारी एकत्र करने की आवश्यकता हो सकती है जैसे कि:
एक वर्ग की गतिशील वस्तुओं की संख्या की गणना करें,
गतिशील आवंटन आदि का उपयोग करके बनाई जा रही वस्तुओं की संख्या को प्रतिबंधित करें।
सब, यह जानकारी कस्टम new
को बदलकर और ओवरलोडेड new
में डायग्नोस्टिक संग्रह तंत्र को हटाकर और हटाकर एकत्र की जा सकती है।
new
में उप-स्मृति स्मृति संरेखण की भरपाई करने के लिए:
कई कंप्यूटर आर्किटेक्चर की आवश्यकता होती है कि विशेष प्रकार के डेटा को विशेष प्रकार के पते पर स्मृति में रखा जाए। उदाहरण के लिए, एक आर्किटेक्चर की आवश्यकता हो सकती है कि पॉइंटर्स उन पतों पर होते हैं जो चार में से एक हैं (यानी, चार-बाइट गठबंधन हो) या यह कि आठों में से एक के बराबर पते पर युगल होना चाहिए (यानी आठ-बाइट गठबंधन हो)। ऐसी बाधाओं का पालन करने में विफलता रन-टाइम पर हार्डवेयर अपवादों का कारण बन सकती है। अन्य आर्किटेक्चर अधिक क्षमाशील हैं, और प्रदर्शन को कम करने के बावजूद इसे काम करने की अनुमति दे सकते हैं। ऑपरेटर new
कि कुछ कंपाइलर्स के साथ जहाज डबल्स के गतिशील आवंटन के लिए आठ बाइट संरेखण की गारंटी नहीं देता है। ऐसे मामलों में, डिफॉल्ट ऑपरेटर को एक के साथ बदलकर जो आठ-बाइट संरेखण की गारंटी देता है, प्रोग्राम प्रदर्शन में बड़ी वृद्धि कर सकता है और new
को बदलने और ऑपरेटरों को delete
एक अच्छा कारण हो सकता है।
एक दूसरे के पास संबंधित वस्तुओं को क्लस्टर करने के लिए:
यदि आप जानते हैं कि विशेष डेटा संरचनाओं का आम तौर पर एक साथ उपयोग किया जाता है और आप डेटा पर काम करते समय पृष्ठ दोषों की आवृत्ति को कम करना चाहते हैं, तो डेटा संरचनाओं के लिए एक अलग ढेर बनाने के लिए यह समझ में आता है ताकि वे कुछ के साथ क्लस्टर हो जाएं जितना संभव हो पेज। new
और delete
कस्टम प्लेसमेंट संस्करण इस तरह के क्लस्टरिंग को हासिल करना संभव कर सकते हैं।
अपरंपरागत व्यवहार प्राप्त करने के लिए:
कभी-कभी आप ऑपरेटरों को नया चाहते हैं और कुछ ऐसा करने के लिए हटाते हैं जो संकलक-प्रदान किए गए संस्करण ऑफ़र नहीं करते हैं।
उदाहरण के लिए: आप एक कस्टम ऑपरेटर delete
लिख delete
जो एप्लिकेशन डेटा की सुरक्षा बढ़ाने के लिए शून्य के साथ विलुप्त स्मृति को ओवरराइट करता है।
सबसे पहले, वास्तव में कई new
और ऑपरेटरों को delete
(वास्तव में एक मनमाना संख्या, वास्तव में)।
सबसे पहले, वहाँ ::operator new
, ::operator new[]
, ::operator delete
और ::operator delete[]
। दूसरा, किसी भी कक्षा X
, X::operator new
, X::operator new[]
, X::operator delete
और X::operator delete[]
।
इनके बीच, वैश्विक ऑपरेटरों की तुलना में कक्षा-विशिष्ट ऑपरेटरों को अधिभारित करना अधिक आम है - किसी विशिष्ट वर्ग के स्मृति उपयोग के लिए यह एक सामान्य पर्याप्त पैटर्न का पालन करने के लिए काफी आम है जिसे आप ऑपरेटरों को लिख सकते हैं जो डिफ़ॉल्ट पर पर्याप्त सुधार प्रदान करते हैं। स्मृति उपयोग को भविष्य में सटीक या विशेष रूप से वैश्विक आधार पर अनुमानित करना आम तौर पर कठिन होता है।
शायद यह भी उल्लेखनीय है कि यद्यपि operator new
और operator new[]
एक दूसरे से अलग हैं (इसी प्रकार किसी भी X::operator new
और X::operator new[]
), दोनों के लिए आवश्यकताओं के बीच कोई अंतर नहीं है। एक को एक ऑब्जेक्ट आवंटित करने के लिए बुलाया जाएगा, और दूसरे ऑब्जेक्ट्स को आवंटित करने के लिए आवंटित किए जाएंगे, लेकिन प्रत्येक को अभी भी आवश्यक स्मृति की मात्रा प्राप्त होती है, और उसे बड़ी संख्या में स्मृति (कम से कम) के पते को वापस करने की आवश्यकता होती है।
आवश्यकताओं की बात करते हुए, अन्य आवश्यकताओं की समीक्षा करना शायद सार्थक है 1 : वैश्विक ऑपरेटरों को वास्तव में वैश्विक होना चाहिए - आप किसी को नामस्थान के अंदर नहीं डाल सकते हैं या एक विशेष अनुवाद इकाई में एक स्थिर बना सकते हैं। दूसरे शब्दों में, केवल दो स्तर हैं जिन पर अधिभार हो सकता है: एक वर्ग-विशिष्ट अधिभार या वैश्विक अधिभार। "नामस्थान एक्स में सभी कक्षाएं" या "अनुवाद इकाई वाई में सभी आवंटन" जैसे बिंदुओं के बीच में अनुमति नहीं है। वर्ग-विशिष्ट ऑपरेटरों को static
होना आवश्यक है - लेकिन आपको वास्तव में उन्हें स्थैतिक घोषित करने की आवश्यकता नहीं है - वे स्थिर होंगे कि आप स्पष्ट रूप से उन्हें static
घोषित करते हैं या नहीं। आधिकारिक तौर पर, वैश्विक ऑपरेटरों ने बहुत अधिक स्मृति को गठबंधन किया ताकि इसका उपयोग किसी भी प्रकार की वस्तु के लिए किया जा सके। अनौपचारिक रूप से, एक संबंध में थोड़ा विग्लू-रूम है: यदि आपको एक छोटे से ब्लॉक (उदाहरण के लिए, 2 बाइट्स) के लिए अनुरोध मिलता है तो आपको उस आकार तक किसी ऑब्जेक्ट के लिए केवल गठबंधन स्मृति प्रदान करने की आवश्यकता होती है, क्योंकि वहां कुछ भी स्टोर करने का प्रयास करने के बाद से वैसे भी अपरिभाषित व्यवहार के लिए नेतृत्व करेंगे।
उन प्राथमिकताओं को कवर करने के बाद, आइए मूल ऑपरेटर पर वापस आएं कि आप इन ऑपरेटरों को अधिभार क्यों लेना चाहते हैं। सबसे पहले, मुझे यह इंगित करना चाहिए कि वैश्विक ऑपरेटरों को अधिभारित करने के कारण कक्षा-विशिष्ट ऑपरेटरों को अधिभारित करने के कारणों से काफी अलग हैं।
चूंकि यह अधिक आम है, मैं पहले क्लास-विशिष्ट ऑपरेटरों के बारे में बात करूंगा। कक्षा-विशिष्ट स्मृति प्रबंधन का प्राथमिक कारण प्रदर्शन है। यह आमतौर पर दो रूपों (या दोनों) में आता है: या तो गति में सुधार, या विखंडन को कम करना। गति इस तथ्य से सुधारा जा सकता है कि मेमोरी मैनेजर केवल एक विशेष आकार के ब्लॉक से निपटता है, इसलिए यह किसी भी समय ब्लॉक करने के बजाय किसी भी ब्लॉक को संबोधित करने के बजाय किसी भी मुक्त ब्लॉक का पता वापस कर सकता है, यदि ब्लॉक काफी बड़ा है, तो दो में ब्लॉक को विभाजित करना बहुत बड़ा, इत्यादि। फ्रैगमेंटेशन (ज्यादातर) एक ही तरीके से कम हो जाता है - उदाहरण के लिए, एन ऑब्जेक्ट्स के लिए पर्याप्त ब्लॉक को पूर्व आवंटित करने से एन ऑब्जेक्ट्स के लिए बिल्कुल आवश्यक जगह मिलती है; एक ऑब्जेक्ट के मेमोरी के लायक आवंटित करने से एक ऑब्जेक्ट के लिए बिल्कुल जगह आवंटित की जाएगी, न कि एक बाइट अधिक।
वैश्विक मेमोरी प्रबंधन ऑपरेटरों को अधिभारित करने के कई कारण हैं। इनमें से कई डिबगिंग या वाद्ययंत्र की ओर उन्मुख हैं, जैसे किसी एप्लिकेशन द्वारा आवश्यक कुल मेमोरी को ट्रैक करना (उदाहरण के लिए, एम्बेडेड सिस्टम पर पोर्टिंग करने की तैयारी में), या मेमोरी आवंटित करने और मुक्त करने के बीच मिस्चैच दिखाकर स्मृति समस्याओं को डीबग करना। एक और आम रणनीति प्रत्येक अनुरोधित ब्लॉक की सीमाओं के पहले और बाद में अतिरिक्त स्मृति आवंटित करना और उन क्षेत्रों में अद्वितीय पैटर्न लिखना है। निष्पादन (और संभवतया अन्य बार भी) के अंत में, उन क्षेत्रों की जांच की जाती है कि कोड आवंटित सीमाओं के बाहर लिखा गया है या नहीं। फिर भी एक और स्वचालित कचरा कलेक्टर के साथ स्मृति आवंटन या हटाने के कम से कम कुछ पहलुओं को स्वचालित करके उपयोग में आसानी को सुधारने का प्रयास करना है।
प्रदर्शन को बेहतर बनाने के लिए एक गैर-डिफ़ॉल्ट ग्लोबल आवंटक का भी उपयोग किया जा सकता है। एक सामान्य मामला एक डिफ़ॉल्ट आवंटक को प्रतिस्थापित करेगा जो सामान्य रूप से धीमा था (उदाहरण के लिए, कम से कम 4.x के आसपास एमएस वीसी ++ के कुछ संस्करण सिस्टम आवंटन / हटाना ऑपरेशन के लिए सिस्टम HeapFree
और HeapFree
फ़ंक्शन को कॉल करेंगे)। अभ्यास में देखा गया एक और संभावना एसएसई संचालन का उपयोग करते समय इंटेल प्रोसेसर पर हुई थी। ये 128-बिट डेटा पर काम करते हैं। जबकि संचालन संरेखण के बावजूद काम करेगा, डेटा 128-बिट सीमाओं के साथ गठबंधन होने पर गति में सुधार हुआ है। कुछ कंपाइलर्स (उदाहरण के लिए, एमएस वीसी ++ दोबारा 2 ) ने उस बड़े सीमा पर संरेखण लागू नहीं किया है, भले ही डिफ़ॉल्ट आवंटक का उपयोग करने वाला कोड काम करेगा, आवंटन को प्रतिस्थापित करने से उन परिचालनों में पर्याप्त गति सुधार हो सकता है।
- अधिकांश आवश्यकताओं को सी ++ मानक के §3.7.3 और §18.4 में शामिल किया गया है (या §3.7.4 और §18.6 सी ++ 0x में, कम से कम N3291 के रूप में)।
- मुझे यह इंगित करने के लिए बाध्यता है कि मैं माइक्रोसॉफ्ट के कंपाइलर को चुनने का इरादा नहीं रखता - मुझे संदेह है कि इसमें ऐसी समस्याओं की असामान्य संख्या है, लेकिन मुझे इसका बहुत उपयोग करना पड़ता है, इसलिए मुझे इसकी समस्याओं के बारे में काफी जानकारी है।