यह ध्यान दिया जाना चाहिए कि यह सी ++ के मामले में, एक आम गलत धारणा है कि "आपको मैनुअल मेमोरी प्रबंधन करने की आवश्यकता है"। वास्तव में, आप आमतौर पर अपने कोड में कोई स्मृति प्रबंधन नहीं करते हैं।
फिक्स्ड-साइज़ ऑब्जेक्ट्स (स्कोप आजीवन)
जब आपको किसी वस्तु की आवश्यकता होती है, तो अधिकांश मामलों में, ऑब्जेक्ट आपके प्रोग्राम में एक परिभाषित जीवनकाल होगा और स्टैक पर बनाया जाता है। यह सभी अंतर्निहित आदिम डेटा प्रकारों के लिए काम करता है, लेकिन वर्गों और संरचनाओं के उदाहरणों के लिए भी:
class MyObject {
public: int x;
};
int objTest()
{
MyObject obj;
obj.x = 5;
return obj.x;
}
फ़ंक्शन समाप्त होने पर स्टैक ऑब्जेक्ट स्वचालित रूप से हटा दिए जाते हैं। जावा में, वस्तुओं को हमेशा ढेर पर बनाया जाता है, और इसलिए कचरा संग्रह जैसे कुछ तंत्र द्वारा हटाया जाना चाहिए। यह स्टैक ऑब्जेक्ट्स के लिए एक गैर-समस्या है।
ऐसी वस्तुएं जो डायनेमिक डेटा का प्रबंधन करती हैं (स्कोप आजीवन)
स्टैक पर जगह का उपयोग करना एक निश्चित आकार की वस्तुओं के लिए काम करता है। जब आपको एक चर राशि की आवश्यकता होती है, जैसे कि एक सरणी, तो एक और दृष्टिकोण का उपयोग किया जाता है: सूची को एक निश्चित आकार की वस्तु में एन्क्रिप्ट किया गया है जो आपके लिए गतिशील मेमोरी का प्रबंधन करती है। यह काम करता है क्योंकि वस्तुओं में एक विशेष सफाई कार्य हो सकता है, विध्वंसक। यह कहा जाता है कि वस्तु के दायरे से बाहर जाने पर और निर्माता के विपरीत कार्य करने की गारंटी दी जाती है:
class MyList {
public:
// a fixed-size pointer to the actual memory.
int* listOfInts;
// constructor: get memory
MyList(size_t numElements) { listOfInts = new int[numElements]; }
// destructor: free memory
~MyList() { delete[] listOfInts; }
};
int listTest()
{
MyList list(1024);
list.listOfInts[200] = 5;
return list.listOfInts[200];
// When MyList goes off stack here, its destructor is called and frees the memory.
}
जिस कोड में मेमोरी का उपयोग किया जाता है, वहां मेमोरी मैनेजमेंट बिल्कुल भी नहीं होता है। हमें केवल यह सुनिश्चित करने की आवश्यकता है कि हमारे द्वारा लिखी गई वस्तु में एक उपयुक्त विध्वंसक है। कोई फर्क नहीं पड़ता कि हम किस तरह से गुंजाइश छोड़ते हैं listTest
, यह एक अपवाद के माध्यम से हो या बस इसे से लौटकर, विध्वंसक ~MyList()
को बुलाया जाएगा और हमें किसी भी मेमोरी को प्रबंधित करने की आवश्यकता नहीं है।
(मुझे लगता है कि यह विध्वंसक को इंगित करने के लिए, बाइनरी नहीं ऑपरेटर का उपयोग करने के लिए एक मज़ेदार डिज़ाइन निर्णय है ~
। जब संख्याओं पर उपयोग किया जाता है, तो यह बिट्स को अक्रिय करता है; अनुरूप में, यह इंगित करता है कि निर्माणकर्ता ने जो किया है वह उलटा है।)
मूल रूप से सभी C ++ ऑब्जेक्ट्स जिन्हें डायनेमिक मेमोरी की आवश्यकता होती है, इस एनकैप्सुलेशन का उपयोग करते हैं। इसे आरएआईआई कहा गया है ("संसाधन अधिग्रहण आरंभीकरण है"), जो सरल विचार को व्यक्त करने का एक अजीब तरीका है जो ऑब्जेक्ट अपनी सामग्री के बारे में परवाह करते हैं; वे जो कुछ हासिल करते हैं, वह है सफाई करना।
पॉलीमॉर्फिक ऑब्जेक्ट और आजीवन दायरे से परे
अब, ये दोनों मामले स्मृति के लिए थे जिनका स्पष्ट रूप से परिभाषित जीवनकाल है: जीवनकाल गुंजाइश के समान है। यदि हम गुंजाइश छोड़ते समय कोई वस्तु समाप्त नहीं करना चाहते हैं, तो एक तीसरा तंत्र है जो हमारे लिए स्मृति का प्रबंधन कर सकता है: एक स्मार्ट पॉइंटर। स्मार्ट पॉइंटर्स का उपयोग तब भी किया जाता है जब आपके पास उन वस्तुओं के उदाहरण होते हैं जिनका प्रकार रनटाइम में भिन्न होता है, लेकिन जिनके पास एक सामान्य इंटरफ़ेस या बेस क्लास होता है:
class MyDerivedObject : public MyObject {
public: int y;
};
std::unique_ptr<MyObject> createObject()
{
// actually creates an object of a derived class,
// but the user doesn't need to know this.
return std::make_unique<MyDerivedObject>();
}
int dynamicObjTest()
{
std::unique_ptr<MyObject> obj = createObject();
obj->x = 5;
return obj->x;
// At scope end, the unique_ptr automatically removes the object it contains,
// calling its destructor if it has one.
}
std::shared_ptr
कई ग्राहकों के बीच वस्तुओं को साझा करने के लिए एक और तरह का स्मार्ट पॉइंटर है। अंतिम क्लाइंट के दायरे से बाहर होने पर वे केवल अपनी निहित वस्तु को हटाते हैं, इसलिए उनका उपयोग उन स्थितियों में किया जा सकता है जहां यह पूरी तरह से अज्ञात है कि कितने ग्राहक होंगे और वे कब तक वस्तु का उपयोग करेंगे।
सारांश में, हम देखते हैं कि आप वास्तव में कोई मैनुअल मेमोरी प्रबंधन नहीं करते हैं। सब कुछ एनकैप्सुलेटेड है और फिर पूरी तरह से स्वचालित, स्कोप-आधारित मेमोरी प्रबंधन के माध्यम से देखभाल की जाती है। उन मामलों में जहां यह पर्याप्त नहीं है, स्मार्ट पॉइंटर्स का उपयोग किया जाता है जो कच्ची मेमोरी को एन्क्रिप्ट करते हैं।
C ++ कोड में कहीं भी संसाधन स्वामियों के रूप में कच्चे पॉइंटर्स का उपयोग करना बेहद बुरा व्यवहार माना जाता है, बिल्डरों के बाहर कच्चे आवंटन, और delete
विध्वंसक के बाहर कच्चे कॉल, क्योंकि अपवाद होने पर उन्हें प्रबंधित करना लगभग असंभव है, और आम तौर पर सुरक्षित रूप से उपयोग करना मुश्किल है।
सबसे अच्छा: यह सभी प्रकार के संसाधनों के लिए काम करता है
RAII का एक सबसे बड़ा लाभ यह है कि यह स्मृति तक सीमित नहीं है। यह वास्तव में फाइल और सॉकेट (खोलने / बंद करने) और सिंक्रनाइज़ेशन तंत्र जैसे म्यूटेक्स (लॉकिंग / अनलॉकिंग) जैसे संसाधनों का प्रबंधन करने के लिए एक बहुत ही स्वाभाविक तरीका प्रदान करता है। मूल रूप से, प्राप्त किया जा सकने वाला प्रत्येक संसाधन C ++ में ठीक उसी तरह से प्रबंधित किया जाना चाहिए और जारी किया जाना चाहिए, और इस प्रबंधन में से कोई भी उपयोगकर्ता के लिए नहीं छोड़ा गया है। यह उन सभी वर्गों में निहित है जो निर्माणकर्ता में अधिग्रहण करते हैं और विनाशकारी में जारी करते हैं।
उदाहरण के लिए, एक म्यूटेक्स को लॉक करने वाला एक फ़ंक्शन आमतौर पर C ++ में इस तरह लिखा जाता है:
void criticalSection() {
std::scoped_lock lock(myMutex); // scoped_lock locks the mutex
doSynchronizedStuff();
} // myMutex is released here automatically
अन्य भाषाएं इसे और अधिक जटिल बनाती हैं, या तो आपको मैन्युअल रूप से ऐसा करने की आवश्यकता होती है (जैसे एक finally
खंड में) या वे विशेष तंत्र को स्पॉन करते हैं जो इस समस्या को हल करते हैं, लेकिन विशेष रूप से सुरुचिपूर्ण तरीके से नहीं (आमतौर पर बाद में उनके जीवन में, जब पर्याप्त लोग होते हैं कमी से पीड़ित)। इस तरह के तंत्र जावा में और C # में उपयोग स्टेटमेंट -टू-रिसोर्स हैं , दोनों ही C ++ के RAII के सन्निकटन हैं।
इसलिए, यह समझने के लिए, यह सब C ++ में RAII का एक बहुत ही सतही खाता था, लेकिन मुझे उम्मीद है कि यह पाठकों को यह समझने में मदद करता है कि C ++ में मेमोरी और यहां तक कि संसाधन प्रबंधन भी "मैनुअल" नहीं है, लेकिन वास्तव में ज्यादातर स्वचालित है।