c++ - सी++ प्रोग्राम्स




सी++ प्रोग्रामर को 'नए' के उपयोग को कम क्यों करना चाहिए? (12)

क्योंकि ढेर तेज और मूर्खतापूर्ण है

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

मैं std :: string <std :: string> का उपयोग करते समय std :: स्ट्रिंग के साथ स्टैक ओवरफ़्लो प्रश्न मेमोरी लीक पर ठोकर खाई, और टिप्पणियों में से एक यह कहता है:

इतना new इस्तेमाल करना बंद करो। मैं आपके द्वारा कहीं भी नए इस्तेमाल किए गए किसी भी कारण को नहीं देख सकता। आप सी ++ में मूल्य से वस्तुओं को बना सकते हैं और यह भाषा का उपयोग करने के लिए एक बड़ा लाभ है। आपको ढेर पर सबकुछ आवंटित करने की आवश्यकता नहीं है। जावा प्रोग्रामर की तरह सोचना बंद करो।

मुझे सच में यकीन नहीं है कि उसका क्या मतलब है। ऑब्जेक्ट को सी ++ में जितनी बार संभव हो सके मूल्य से बनाया जाना चाहिए, और यह आंतरिक रूप से क्या अंतर करता है? क्या मैंने जवाब गलत व्याख्या किया?


क्योंकि यह सूक्ष्म रिसाव के लिए प्रवण होता है भले ही आप परिणाम को एक स्मार्ट सूचक में लपेटें

एक "सावधान" उपयोगकर्ता पर विचार करें जो स्मार्ट पॉइंटर्स में वस्तुओं को लपेटने के लिए याद करता है:

foo(shared_ptr<T1>(new T1()), shared_ptr<T2>(new T2()));

यह कोड खतरनाक है क्योंकि इस बात की कोई गारंटी नहीं है कि या तो shared_ptr या तो T1 या T2 से पहले बनाया गया है। इसलिए, यदि अन्य सफल होने के बाद new T2() new T1() या new T2() विफल रहता है, तो पहली वस्तु लीक हो जाएगी क्योंकि कोई shared_ptr इसे नष्ट करने और इसे shared_ptr मौजूद नहीं है।

SolutIon: make_shared का उपयोग make_shared


जब आप नए का उपयोग करते हैं, तो वस्तुओं को ढेर में आवंटित किया जाता है। जब आप विस्तार की उम्मीद करते हैं तो आमतौर पर इसका उपयोग किया जाता है। जब आप एक वस्तु घोषित करते हैं जैसे कि,

Class var;

यह ढेर पर रखा गया है।

आपको उस ऑब्जेक्ट पर हमेशा नष्ट करना होगा जिसे आपने नए ढेर पर रखा था। यह स्मृति रिसाव की संभावना खुलता है। ढेर पर रखे ऑब्जेक्ट्स स्मृति लीकिंग के लिए प्रवण नहीं हैं!


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

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

हालांकि, अभी भी समस्या से बचने के लिए बेहतर है। यदि आप इसे ढेर पर डाल सकते हैं, तो ऐसा करें।


दो व्यापक रूप से उपयोग की जाने वाली स्मृति आवंटन तकनीकें हैं: स्वचालित आवंटन और गतिशील आवंटन। आम तौर पर, प्रत्येक के लिए स्मृति का एक समान क्षेत्र होता है: ढेर और ढेर।

ढेर

ढेर हमेशा अनुक्रमिक फैशन में स्मृति आवंटित करता है। ऐसा इसलिए हो सकता है क्योंकि आपको स्मृति को रिवर्स ऑर्डर (फर्स्ट-इन, लास्ट-आउट: फिल्म) में रिलीज़ करने की आवश्यकता होती है। यह कई प्रोग्रामिंग भाषाओं में स्थानीय चर के लिए स्मृति आवंटन तकनीक है। यह बहुत तेज़ है क्योंकि इसे न्यूनतम बहीखाता की आवश्यकता होती है और आवंटित करने का अगला पता निहित है।

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

ढेर

ढेर एक अधिक लचीला स्मृति आवंटन मोड के लिए अनुमति देता है। बहीखाता अधिक जटिल है और आवंटन धीमा है। चूंकि कोई अंतर्निहित रिलीज पॉइंट नहीं है, इसलिए आपको delete या delete[] (सी में free ) का उपयोग करके मैन्युअल रूप से मेमोरी को रिलीज़ करना होगा। हालांकि, एक निहित रिलीज बिंदु की अनुपस्थिति ढेर की लचीलापन की कुंजी है।

गतिशील आवंटन का उपयोग करने के कारण

यहां तक ​​कि यदि ढेर का उपयोग धीमा है और संभावित रूप से मेमोरी लीक या मेमोरी विखंडन की ओर जाता है, तो गतिशील आवंटन के लिए पूरी तरह से अच्छे उपयोग के मामले हैं, क्योंकि यह कम सीमित है।

गतिशील आवंटन का उपयोग करने के दो मुख्य कारण:

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

  • आप स्मृति को आवंटित करना चाहते हैं जो वर्तमान ब्लॉक को छोड़ने के बाद जारी रहेगा। उदाहरण के लिए, आप एक फ़ंक्शन string readfile(string path) लिखना चाह सकते हैं जो फ़ाइल की सामग्री देता है। इस मामले में, भले ही स्टैक पूरी फ़ाइल सामग्री को पकड़ सके, आप फ़ंक्शन से वापस नहीं आ सकते और आवंटित स्मृति ब्लॉक को रोक सकते हैं।

गतिशील आवंटन अक्सर अनावश्यक क्यों होता है

सी ++ में एक विनाशक नामक एक साफ रचना है। यह तंत्र आपको एक चर के जीवनकाल के साथ संसाधन के जीवनकाल को संरेखित करके संसाधनों का प्रबंधन करने की अनुमति देता है। इस तकनीक को RAII कहा जाता है और सी ++ का विशिष्ट बिंदु है। यह वस्तुओं में वस्तुओं को "लपेटता है"। std::string एक आदर्श उदाहरण है। यह स्निपेट:

int main ( int argc, char* argv[] )
{
    std::string program(argv[0]);
}

वास्तव में स्मृति की एक परिवर्तनीय राशि आवंटित करता है। std::string ऑब्जेक्ट ढेर का उपयोग करके स्मृति आवंटित करता है और इसे अपने विनाशक में रिलीज़ करता है। इस मामले में, आपको किसी भी संसाधन को मैन्युअल रूप से प्रबंधित करने की आवश्यकता नहीं थी और अभी भी गतिशील स्मृति आवंटन के लाभ प्राप्त हुए।

विशेष रूप से, इसका तात्पर्य है कि इस स्निपेट में:

int main ( int argc, char* argv[] )
{
    std::string * program = new std::string(argv[0]);  // Bad!
    delete program;
}

अनियंत्रित गतिशील स्मृति आवंटन है। कार्यक्रम के लिए अधिक टाइपिंग (!) की आवश्यकता है और स्मृति को हटाने के लिए भूलने का जोखिम प्रस्तुत करता है। यह बिना किसी स्पष्ट लाभ के करता है।

आपको जितनी बार संभव हो सके स्वचालित संग्रहण का उपयोग करना चाहिए

असल में, अंतिम पैराग्राफ इसे बताता है। जितनी बार संभव हो सके स्वचालित संग्रहण का उपयोग करना आपके प्रोग्राम बनाता है:

  • टाइप करने के लिए तेज़;
  • दौड़ते समय तेज;
  • स्मृति / संसाधन रिसाव के लिए कम प्रवण।

बोनस अंक

संदर्भित प्रश्न में, अतिरिक्त चिंताएं हैं। विशेष रूप से, निम्नलिखित वर्ग:

class Line {
public:
    Line();
    ~Line();
    std::string* mString;
};

Line::Line() {
    mString = new std::string("foo_bar");
}

Line::~Line() {
    delete mString;
}

वास्तव में निम्नलिखित के मुकाबले उपयोग करने के लिए बहुत अधिक जोखिम भरा है:

class Line {
public:
    Line();
    std::string mString;
};

Line::Line() {
    mString = "foo_bar";
    // note: there is a cleaner way to write this.
}

कारण यह है कि std::string ठीक से एक कॉपी कन्स्ट्रक्टर को परिभाषित करता है। निम्नलिखित कार्यक्रम पर विचार करें:

int main ()
{
    Line l1;
    Line l2 = l1;
}

मूल संस्करण का उपयोग करके, यह प्रोग्राम संभवतः क्रैश हो जाएगा, क्योंकि यह दो बार एक ही स्ट्रिंग पर delete का उपयोग करता है। संशोधित संस्करण का उपयोग करके, प्रत्येक Line इंस्टेंस का अपना स्ट्रिंग इंस्टेंस होगा , प्रत्येक की अपनी याददाश्त होगी और दोनों प्रोग्राम के अंत में रिलीज़ हो जाएंगे।

अन्य नोट

ऊपर दिए गए सभी कारणों से RAII का व्यापक उपयोग सी ++ में सबसे अच्छा अभ्यास माना जाता है। हालांकि, एक अतिरिक्त लाभ है जो तुरंत स्पष्ट नहीं है। असल में, यह इसके हिस्सों के योग से बेहतर है। पूरी तंत्र composes । यह तराजू

यदि आप Line बिल्डिंग को बिल्डिंग ब्लॉक के रूप में उपयोग करते हैं:

 class Table
 {
      Line borders[4];
 };

फिर

 int main ()
 {
     Table table;
 }

चार std::string उदाहरण आवंटित करता है, चार Line उदाहरण, एक Table उदाहरण और सभी स्ट्रिंग की सामग्री और सबकुछ स्वचालित रूप से मुक्त हो जाता है


मुख्य कारण यह है कि ढेर पर वस्तुओं को सरल मूल्यों से उपयोग करना और प्रबंधित करना हमेशा मुश्किल होता है। लेखन कोड जो पढ़ने और बनाए रखने में आसान है, हमेशा किसी भी गंभीर प्रोग्रामर की पहली प्राथमिकता है।

एक अन्य परिदृश्य वह पुस्तकालय है जिसका उपयोग हम कर रहे हैं मूल्य अर्थशास्त्र प्रदान करता है और गतिशील आवंटन अनावश्यक बनाता है। Std::string एक अच्छा उदाहरण है।

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

पारंपरिक डिजाइन पैटर्न, विशेष रूप से जो GoF बुक में उल्लेखित हैं, बहुत new उपयोग करते हैं, क्योंकि वे सामान्य ओओ कोड हैं।


मैं देखता हूं कि जितना संभव हो उतना नया करने के कुछ महत्वपूर्ण कारण मिस्ड हैं:

ऑपरेटर के पास एक गैर-निर्धारिती निष्पादन समय होता है

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

ऑपरेटर new एक अंतर्निहित थ्रेड सिंक्रनाइज़ेशन है

हां आपने मुझे सुना है, आपके ओएस को यह सुनिश्चित करने की ज़रूरत है कि आपकी पेज टेबल सुसंगत हैं और इस तरह के new कॉलिंग से आपके थ्रेड को एक अंतर्निहित म्यूटेक्स लॉक प्राप्त होगा। यदि आप लगातार कई धागे से new कॉल कर रहे हैं तो आप वास्तव में अपने धागे को क्रमबद्ध कर रहे हैं (मैंने इसे 32 सीपीयू के साथ किया है, प्रत्येक को कुछ सौ बाइट प्राप्त करने के लिए new पर मारना, आउच! वह डीबग करने के लिए शाही पिटा था)

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


मैं नए "बहुत ज्यादा" का उपयोग करने के विचार से असहमत हूं। यद्यपि मूल पोस्टर सिस्टम कक्षाओं के साथ नए का उपयोग थोड़ा हास्यास्पद है। ( int *i; i = new int[9999]; सचमुच? int i[9999]; बहुत स्पष्ट है।) मुझे लगता है कि टिप्पणीकर्ता की बकरी मिल रही थी।

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

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


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

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

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


new द्वारा बनाए गए ऑब्जेक्ट्स को अंततः delete जाना चाहिए ताकि वे रिसाव न करें। विनाशक नहीं कहा जाएगा, स्मृति मुक्त नहीं किया जाएगा, पूरी बात। चूंकि सी ++ में कोई कचरा संग्रह नहीं है, यह एक समस्या है।

मूल्य (यानी ढेर पर) द्वारा बनाए गए ऑब्जेक्ट्स जब वे दायरे से बाहर निकलते हैं तो स्वचालित रूप से मर जाते हैं। नियंत्रक कॉल को संकलक द्वारा डाला जाता है, और मेमोरी फ़ंक्शन रिटर्न पर स्वतः मुक्त होती है।

auto_ptr जैसे स्मार्ट पॉइंटर्स, shared_ptr खतरनाक संदर्भ समस्या को हल करते हैं, लेकिन उन्हें कोडिंग अनुशासन की आवश्यकता होती है और अन्य समस्याएं होती हैं (प्रतिलिपि, संदर्भ लूप, आदि)।

इसके अलावा, भारी बहुप्रचारित परिदृश्यों में, new धागे के बीच विवाद का एक बिंदु है; new उपयोग करने के लिए एक प्रदर्शन प्रभाव हो सकता है। स्टैक ऑब्जेक्ट सृजन परिभाषा थ्रेड-लोकल द्वारा है, क्योंकि प्रत्येक थ्रेड का अपना ढेर होता है।

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


newढेर पर वस्तुओं आवंटित करता है। अन्यथा, ढेर पर वस्तुओं आवंटित किए जाते हैं। दोनों के बीच अंतर देखो ।


  • सी ++ किसी भी मेमोरी मैनेजर को स्वयं ही नियोजित नहीं करता है। सी #, जावा जैसी अन्य भाषाओं में मेमोरी को संभालने के लिए कचरा कलेक्टर है
  • ऑपरेटिंग सिस्टम रूटीन का उपयोग कर सी ++ मेमोरी आवंटित करने के लिए और बहुत अधिक नया / हटाएं उपलब्ध स्मृति को खंडित कर सकता है
  • किसी भी आवेदन के साथ, यदि स्मृति का अक्सर उपयोग किया जा रहा है तो इसे पूर्व आवंटित करने और आवश्यकता होने पर रिलीज करने की सलाह दी जाती है।
  • अनुचित स्मृति प्रबंधन मेमोरी लीक का नेतृत्व कर सकता है और ट्रैक करना वाकई मुश्किल है। इसलिए कार्य के दायरे में ढेर वस्तुओं का उपयोग करना एक सिद्ध तकनीक है
  • स्टैक ऑब्जेक्ट्स का उपयोग करने का नकारात्मक हिस्सा यह है कि यह लौटने पर वस्तुओं की कई प्रतियां बनाता है, कार्यों को पारित करता है। हालांकि स्मार्ट कंपाइलर्स इन परिस्थितियों के बारे में अच्छी तरह से जानते हैं और उन्हें प्रदर्शन के लिए अनुकूलित किया गया है
  • यदि स्मृति दो आवंटित किया जाता है और दो अलग-अलग स्थानों में जारी किया जाता है तो यह वास्तव में सी ++ में थकाऊ है। रिलीज की ज़िम्मेदारी हमेशा एक सवाल है और ज्यादातर हम कुछ सामान्य सुलभ पॉइंटर्स पर भरोसा करते हैं, स्टैक ऑब्जेक्ट्स (अधिकतम संभव) और auto_ptr (RAII ऑब्जेक्ट्स) जैसी तकनीकें
  • सबसे अच्छी बात यह है कि, आप स्मृति पर नियंत्रण रखते हैं और सबसे बुरी बात यह है कि यदि आप एप्लिकेशन के लिए अनुचित स्मृति प्रबंधन को नियोजित करते हैं तो आपके पास स्मृति पर कोई नियंत्रण नहीं होगा। स्मृति भ्रष्टाचार के कारण होने वाली दुर्घटनाएं सबसे नास्टीस्ट और ट्रेस करने के लिए कठिन हैं।




c++-faq