pointers - मुझे C++में नए कीवर्ड का उपयोग कब करना चाहिए?




reference new-operator keyword (10)

मैं थोड़ी देर के लिए सी ++ का उपयोग कर रहा हूं, और मैं नए कीवर्ड के बारे में सोच रहा हूं। बस, क्या मुझे इसका इस्तेमाल करना चाहिए, या नहीं?

1) नए कीवर्ड के साथ ...

MyClass* myClass = new MyClass();
myClass->MyField = "Hello world!";

2) नए कीवर्ड के बिना ...

MyClass myClass;
myClass.MyField = "Hello world!";

एक कार्यान्वयन परिप्रेक्ष्य से, वे अलग नहीं लगते हैं (लेकिन मुझे यकीन है कि वे हैं) ... हालांकि, मेरी प्राथमिक भाषा सी # है, और निश्चित रूप से पहली विधि है जिसका उपयोग मैं करता हूं।

कठिनाई यह प्रतीत होती है कि विधि 1 std C ++ कक्षाओं के साथ उपयोग करना कठिन है।

मुझे किस विधि का उपयोग करना चाहिए?

अद्यतन 1:

मैंने हाल ही में एक बड़े सरणी के लिए हीप मेमोरी (या फ्री स्टोर ) के लिए नया कीवर्ड इस्तेमाल किया जो कि दायरे से बाहर जा रहा था (यानी एक फ़ंक्शन से वापस किया जा रहा था)। इससे पहले कि मैं ढेर का उपयोग कर रहा था, जिसके कारण तत्वों का आधा हिस्सा गुंजाइश के बाहर भ्रष्ट हो गया था, ढेर के उपयोग में स्विच करने से यह सुनिश्चित हुआ कि तत्व सामंजस्य में थे। वाह!

अद्यतन 2:

मेरे एक दोस्त ने हाल ही में मुझे बताया कि new कीवर्ड का उपयोग करने के लिए एक सरल नियम है; हर बार जब आप new टाइप new , तो delete टाइप करें।

Foobar *foobar = new Foobar();
delete foobar; // TODO: Move this to the right place.

यह स्मृति रिसाव को रोकने में मदद करता है, क्योंकि आपको हमेशा कहीं हटाना पड़ता है (यानी जब आप इसे किसी विनाशक या अन्यथा चिपकाते हैं)।


Answers

new कीवर्ड के बिना आप कॉल स्टैक पर संग्रहीत कर रहे हैं। ढेर पर अत्यधिक बड़े चर भंडारण से ढेर ओवरफ्लो हो जाएगा


दोनों के बीच एक महत्वपूर्ण अंतर है।

सब कुछ new साथ आवंटित नहीं किया जाता है, सी # में मूल्य प्रकारों की तरह व्यवहार करता है (और लोग अक्सर कहते हैं कि उन वस्तुओं को ढेर पर आवंटित किया जाता है, जो शायद सबसे आम / स्पष्ट मामला है, लेकिन हमेशा सत्य नहीं है। अधिक सटीक, new उपयोग किए बिना आवंटित ऑब्जेक्ट्स स्वचालित भंडारण अवधि new साथ आवंटित सब कुछ ढेर पर आवंटित किया जाता है, और इसके लिए एक सूचक वापस लौटाया जाता है, ठीक उसी तरह संदर्भ प्रकार सी # में।

ढेर पर आवंटित कुछ भी स्थिर आकार होना चाहिए, संकलन समय पर निर्धारित (संकलक को स्टैक पॉइंटर को सही ढंग से सेट करना होगा, या यदि ऑब्जेक्ट किसी अन्य वर्ग का सदस्य है, तो उसे उस अन्य वर्ग के आकार को समायोजित करना होगा) । यही कारण है कि सी # में सरणी संदर्भ प्रकार हैं। उन्हें होना चाहिए, क्योंकि संदर्भ प्रकारों के साथ, हम रनटाइम पर निर्णय ले सकते हैं कि कितनी मेमोरी पूछनी है। और यह वही लागू होता है। निरंतर आकार के साथ केवल सरणी (एक आकार जिसे संकलन-समय पर निर्धारित किया जा सकता है) स्वचालित संग्रहण अवधि (ढेर पर) के साथ आवंटित किया जा सकता है। गतिशील रूप से आकार वाले सरणी को new कॉल करके ढेर पर आवंटित किया जाना चाहिए।

(और वह वहीं है जहां सी # स्टॉप की कोई समानता है)

अब, स्टैक पर आवंटित कुछ भी "स्वचालित" भंडारण अवधि है (आप वास्तव में एक वैरिएबल को auto रूप में घोषित कर सकते हैं, लेकिन यह डिफ़ॉल्ट है यदि कोई अन्य संग्रहण प्रकार निर्दिष्ट नहीं है, तो कीवर्ड वास्तव में अभ्यास में उपयोग नहीं किया जाता है, लेकिन यह वह जगह है यह इससे आता है)

स्वचालित भंडारण अवधि का अर्थ यह है कि यह कैसा लगता है, चर की अवधि स्वचालित रूप से संभाली जाती है। इसके विपरीत, ढेर पर आवंटित कुछ भी आपके द्वारा मैन्युअल रूप से हटा दिया जाना चाहिए। यहां एक उदाहरण दिया गया है:

void foo() {
  bar b;
  bar* b2 = new bar();
}

यह फ़ंक्शन विचार करने के लायक तीन मान बनाता है:

लाइन 1 पर, यह स्टैक (स्वचालित अवधि) पर एक प्रकार की प्रकार bar घोषित करता है।

लाइन 2 पर, यह स्टैक (स्वचालित अवधि) पर एक bar पॉइंटर b2 घोषित करता है, और ढेर पर एक bar ऑब्जेक्ट आवंटित करने के लिए नया कॉल करता है। (गतिशील अवधि)

जब समारोह वापस आता है, तो निम्नलिखित होगा: सबसे पहले, b2 गुंजाइश से बाहर हो जाता है (विनाश का आदेश हमेशा निर्माण के क्रम के विपरीत होता है)। लेकिन b2 सिर्फ एक सूचक है, इसलिए कुछ नहीं होता है, जो स्मृति वह कब्जा कर लेती है वह बस मुक्त होती है। और महत्वपूर्ण बात यह है कि यह स्मृति जो इंगित करती है (ढेर पर bar उदाहरण) स्पर्श नहीं होता है। केवल सूचक को मुक्त किया जाता है, क्योंकि केवल सूचक के पास स्वचालित अवधि होती है। दूसरा, b गुंजाइश से बाहर चला जाता है, इसलिए इसकी अवधि स्वचालित है, इसके विनाशक को बुलाया जाता है, और स्मृति मुक्त हो जाती है।

और ढेर पर bar उदाहरण? यह शायद अभी भी वहाँ है। इसे हटाने के लिए कोई भी परेशान नहीं है, इसलिए हमने स्मृति लीक की है।

इस उदाहरण से, हम देख सकते हैं कि स्वचालित अवधि के साथ कुछ भी इसकी विनाशक होने की गारंटी है जब यह गुंजाइश से बाहर हो जाता है। यह उपयोगी है। लेकिन ढेर पर आवंटित कुछ भी तब तक चलता है जब तक हमें इसकी आवश्यकता होती है, और गतिशील रूप से आकार दिया जा सकता है, जैसा कि सरणी के मामले में होता है। यह भी उपयोगी है। हम इसका उपयोग हमारे स्मृति आवंटन को प्रबंधित करने के लिए कर सकते हैं। क्या होगा यदि फू क्लास ने अपने कन्स्ट्रक्टर में ढेर पर कुछ मेमोरी आवंटित की, और उस स्मृति को अपने विनाशक में हटा दिया। फिर हम दोनों दुनिया के सर्वश्रेष्ठ, सुरक्षित स्मृति आवंटन को फिर से मुक्त करने की गारंटी दी जा सकती है, लेकिन स्टैक पर सबकुछ मजबूर करने की सीमाओं के बिना।

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

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

एक सामान्य नियम के रूप में, अपने उच्च स्तर कोड से सीधे नए / हटाएं का उपयोग न करें। हमेशा इसे उस कक्षा में लपेटें जो आपके लिए स्मृति का प्रबंधन कर सके, और यह सुनिश्चित करेगा कि यह फिर से मुक्त हो जाए। (हां, इस नियम के अपवाद हो सकते हैं। विशेष रूप से, स्मार्ट पॉइंटर्स के लिए आपको सीधे new कॉल करने की आवश्यकता होती है, और पॉइंटर को अपने कन्स्ट्रक्टर को पास करने की आवश्यकता होती है, जो तब लेता है और सुनिश्चित करता है कि delete सही ढंग से कहा जाता है। लेकिन यह अभी भी एक बहुत ही महत्वपूर्ण नियम है अंगूठे का)


क्या आप MyClass को फ़ंक्शन से बाहर कर रहे हैं, या उस फ़ंक्शन के बाहर मौजूद होने की उम्मीद कर रहे हैं? जैसा कि कुछ अन्य ने कहा, जब आप ढेर पर आवंटित नहीं कर रहे हैं, तो यह सब कुछ गुंजाइश है। जब आप फ़ंक्शन छोड़ते हैं, तो यह दूर हो जाता है (अंत में)। शुरुआती लोगों द्वारा बनाई गई क्लासिक गलतियों में से एक एक समारोह में कुछ वर्ग की स्थानीय वस्तु बनाने का प्रयास है और इसे ढेर पर आवंटित किए बिना इसे वापस कर देता है। मैं अपने पहले दिनों में सी ++ कर इस तरह की चीज को वापस डीबग करना याद कर सकता हूं।


मुझे किस विधि का उपयोग करना चाहिए?

यह लगभग आपकी टाइपिंग वरीयताओं द्वारा संदर्भित नहीं है बल्कि संदर्भ द्वारा। यदि आपको वस्तु को कुछ ढेर में रखने की आवश्यकता है या यदि यह ढेर के लिए बहुत भारी है तो आप इसे मुफ्त स्टोर पर आवंटित करते हैं। साथ ही, चूंकि आप ऑब्जेक्ट आवंटित कर रहे हैं, इसलिए आप स्मृति को जारी करने के लिए भी जिम्मेदार हैं। delete ऑपरेटर को देखो।

फ्री-स्टोर प्रबंधन का उपयोग करने के बोझ को कम करने के लिए लोगों ने auto_ptr और unique_ptr जैसे सामानों का आविष्कार किया है। मैं दृढ़ता से अनुशंसा करता हूं कि आप इन पर नज़र डालें। वे आपके टाइपिंग मुद्दों में भी मदद कर सकते हैं ;-)


विधि 1 ( new उपयोग करके)

  • ऑब्जेक्ट के लिए फ्री स्टोर पर मेमोरी आवंटित करता है (यह अक्सर ढेर के समान ही होता है)
  • आपको बाद में अपनी ऑब्जेक्ट को स्पष्ट रूप से delete आवश्यकता है। (यदि आप इसे हटा नहीं देते हैं, तो आप मेमोरी रिसाव बना सकते हैं)
  • जब तक आप इसे delete नहीं देते तब तक स्मृति आवंटित रहता है। (यानी आप एक ऑब्जेक्ट return कर सकते return जिसे आपने new का उपयोग करके बनाया है)
  • प्रश्न में उदाहरण स्मृति को रिसाव करेगा जब तक कि सूचक delete जाता है; और इसे हमेशा हटाया जाना चाहिए , भले ही कौन सा नियंत्रण पथ लिया जाता है, या अपवाद फेंक दिया जाता है।

विधि 2 ( new उपयोग नहीं कर रहा है)

  • स्टैक पर ऑब्जेक्ट के लिए मेमोरी आवंटित करता है (जहां सभी स्थानीय चर जाते हैं) आमतौर पर ढेर के लिए कम स्मृति उपलब्ध होती है; यदि आप बहुत सारी वस्तुओं को आवंटित करते हैं, तो आप स्टैक ओवरफ़्लो को जोखिम देते हैं।
  • आपको बाद में इसे delete आवश्यकता नहीं होगी।
  • स्कोप से बाहर होने पर स्मृति अब आवंटित नहीं की जाती है। (यानी आपको स्टैक पर ऑब्जेक्ट में पॉइंटर return नहीं करना चाहिए)

जहां तक ​​उपयोग करने के लिए एक; आप उपरोक्त बाधाओं को देखते हुए, उस विधि को चुनते हैं जो आपके लिए सबसे अच्छा काम करता है।

कुछ आसान मामले:

  • यदि आप delete करने के बारे में चिंता नहीं करना चाहते हैं, (और मेमोरी लीक का कारण बनने की क्षमता) तो आपको new उपयोग नहीं करना चाहिए।
  • यदि आप किसी फ़ंक्शन से अपनी ऑब्जेक्ट में पॉइंटर वापस करना चाहते हैं, तो आपको new उपयोग करना होगा

दूसरी विधि स्टैक पर उदाहरण बनाती है, ऐसी चीजों के साथ-साथ कुछ घोषित int और फ़ंक्शन में पारित पैरामीटर की सूची।

पहली विधि स्टैक पर एक पॉइंटर के लिए जगह बनाती है, जिसे आपने स्मृति में स्थान पर सेट किया है जहां एक नया MyClass ढेर - या मुफ्त स्टोर पर आवंटित किया गया है।

पहली विधि के लिए यह भी आवश्यक है कि आप जो भी new बनाते हैं उसे delete , जबकि दूसरी विधि में, कक्षा स्वचालित रूप से नष्ट हो जाती है और जब यह दायरे से बाहर हो जाती है (आमतौर पर अगली बंद ब्रेस, आमतौर पर)।


यदि आपका चर केवल एक फ़ंक्शन के संदर्भ में उपयोग किया जाता है, तो आप एक स्टैक वैरिएबल, यानी विकल्प 2 का उपयोग करना बेहतर कर सकते हैं। जैसा कि अन्य ने कहा है, आपको स्टैक वैरिएबल के जीवनकाल को प्रबंधित करने की आवश्यकता नहीं है - वे निर्मित हैं और स्वचालित रूप से नष्ट हो गया। इसके अलावा, ढेर पर एक चर आवंटित / deallocating तुलना करके धीमा है। यदि आपके फ़ंक्शन को अक्सर पर्याप्त कहा जाता है, तो स्टैक वैरिएबल बनाम ढेर चर का उपयोग करते समय आपको एक शानदार प्रदर्शन सुधार दिखाई देगा।

उस ने कहा, कुछ स्पष्ट उदाहरण हैं जहां स्टैक वैरिएबल अपर्याप्त हैं।

यदि स्टैक वैरिएबल में बड़ी मेमोरी पदचिह्न है, तो आप स्टैक को बहने का जोखिम चलाते हैं। डिफ़ॉल्ट रूप से, प्रत्येक थ्रेड का ढेर आकार विंडोज़ पर 1 एमबी है। यह असंभव है कि आप एक स्टैक वेरिएबल बनाएंगे जो आकार में 1 एमबी है, लेकिन आपको यह ध्यान रखना होगा कि स्टैक उपयोग संचयी है। यदि आपका फ़ंक्शन एक फ़ंक्शन को कॉल करता है जो किसी अन्य फ़ंक्शन को कॉल करता है जो कि किसी अन्य फ़ंक्शन को कॉल करता है ..., इन सभी कार्यों में स्टैक वैरिएबल एक ही स्टैक पर स्थान लेते हैं। रिकर्सिव फ़ंक्शन इस समस्या में जल्दी से चल सकते हैं, इस पर निर्भर करता है कि रिकर्स कितना गहरा है। यदि यह एक समस्या है, तो आप नए ऑपरेटर (अनुशंसित) का उपयोग करके ढेर के आकार को बढ़ा सकते हैं (अनुशंसित नहीं) या चरम पर चर आवंटित कर सकते हैं।

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


सरल जवाब हां है - नया () ढेर पर एक वस्तु बनाता है (दुर्भाग्यपूर्ण दुष्प्रभाव के साथ कि आपको अपने जीवनकाल को प्रबंधित करना है (स्पष्ट रूप से इसे हटाने पर कॉल करना), जबकि दूसरा रूप वर्तमान में ढेर में एक वस्तु बनाता है दायरा और उस वस्तु को नष्ट कर दिया जाएगा जब यह गुंजाइश से बाहर हो जाता है।


यदि आप सी ++ में लिख रहे हैं तो आप शायद प्रदर्शन के लिए लिख रहे हैं। नए और नि: शुल्क स्टोर का उपयोग करना स्टैक (विशेष रूप से धागे का उपयोग करते समय) का उपयोग करने से बहुत धीमा है, इसलिए इसकी आवश्यकता होने पर ही इसका उपयोग करें।

जैसा कि अन्य ने कहा है, आपको नई वस्तु की आवश्यकता होती है जब आपकी ऑब्जेक्ट को फ़ंक्शन या ऑब्जेक्ट स्कोप के बाहर रहने की आवश्यकता होती है, ऑब्जेक्ट वास्तव में बड़ा होता है या जब आप संकलन समय पर किसी सरणी के आकार को नहीं जानते हैं।

साथ ही, हटाने का उपयोग करने से बचने का प्रयास करें। इसके बजाय अपने स्मार्ट को एक पॉइंटर में लपेटें। स्मार्ट पॉइंटर कॉल आपके लिए हटा दें।

ऐसे कुछ मामले हैं जहां एक स्मार्ट सूचक स्मार्ट नहीं है। एसटीएल कंटेनर के अंदर std :: auto_ptr <> को कभी भी स्टोर न करें। कंटेनर के अंदर कॉपी ऑपरेशन की वजह से यह सूचक जल्द ही हटा देगा। एक और मामला तब होता है जब आपके पास ऑब्जेक्ट्स के पॉइंटर्स का वास्तव में बड़ा एसटीएल कंटेनर होता है। boost :: shared_ptr <> में गति ओवरहेड का एक टन होगा क्योंकि यह संदर्भ संख्याओं को ऊपर और नीचे दबाता है। उस मामले में जाने का बेहतर तरीका एसटीएल कंटेनर को किसी अन्य ऑब्जेक्ट में रखना है और उस ऑब्जेक्ट को एक विनाशक देना है जो कंटेनर में प्रत्येक पॉइंटर पर डिलीट करेगा।


विरासत पदानुक्रम के भीतर पॉइंटर्स / संदर्भों को परिवर्तित करने के लिए dynamic_cast उपयोग करें।

सामान्य प्रकार के रूपांतरणों के लिए static_cast प्रयोग करें।

बिट पैटर्न के निम्न-स्तर पुनर्वितरण के लिए reinterpret_cast उपयोग करें। अत्यधिक सावधानी के साथ प्रयोग करें।

दूर const_cast const/volatile कास्टिंग के लिए const_cast प्रयोग करें। तब तक इससे बचें जब तक कि आप एक कॉन्स्ट-गलत API का उपयोग करके फंस गए हों।





c++ pointers reference new-operator keyword