ट्रैक
/
C++
C++
/
सिलेबस
/
स्मार्ट पॉइंटर
स्

स्मार्ट पॉइंटर में C++

1 अभ्यास

स्मार्ट पॉइंटर के बारे में

स्मार्ट पॉइंटर C++ की एक आधुनिक सुविधा हैं। इनका उद्देश्य मेमोरी का अपने-आप प्रबंधन करना है, ताकि मेमोरी लीक और डैंगलिंग पॉइंटर जैसी वे आम समस्याएँ न हों जो रॉ पॉइंटर के साथ आती हैं। ये रॉ पॉइंटर के ऊपर एक रैपर की तरह काम करते हैं और उसमें अतिरिक्त कार्यक्षमता जोड़ते हैं, जैसे पॉइंटर की ज़रूरत खत्म हो जाने पर मेमोरी का अपने-आप मुक्त हो जाना।

सामान्य सिंटैक्स

स्मार्ट पॉइंटर आम तौर पर C++ की स्टैंडर्ड लाइब्रेरी में क्लास टेम्पलेट के रूप में लागू किए जाते हैं। सबसे ज़्यादा इस्तेमाल होने वाले दो स्मार्ट पॉइंटर std::unique_ptr और std::shared_ptr हैं।

यूनिक पॉइंटर

std::unique_ptr एक स्मार्ट पॉइंटर है जो किसी ऑब्जेक्ट का अकेले मालिक होता है। यह सुनिश्चित करता है कि किसी भी समय रिसोर्स का मालिक सिर्फ एक ही std::unique_ptr ऑब्जेक्ट हो। जब मालिक std::unique_ptr नष्ट हो जाता है या रीसेट हो जाता है, तो यह ऑब्जेक्ट को अपने-आप नष्ट कर देता है और उसकी मेमोरी मुक्त कर देता है।

#include <memory>
// Declaring and defining a unique pointer
auto rightful_king_of_england = std::make_unique<std::string>("Excalibur");

// Unique pointers cannot be copied or assigned
auto mordred = rightful_king_of_england; // Error: Cannot copy a unique_ptr

std::make_unique() के फायदे

std::unique_ptr बनाते समय मेमोरी आवंटित करने के लिए सीधे new इस्तेमाल करने की जगह std::make_unique() इस्तेमाल करना बेहतर होता है। std::make_unique() के कई फायदे हैं:

  1. एक्सेप्शन सुरक्षा: std::make_unique() एक्सेप्शन सुरक्षा की गारंटी देता है। अगर ऑब्जेक्ट बनते समय कोई एक्सेप्शन फेंका जाता है, तो मेमोरी अपने-आप मुक्त कर दी जाती है और मेमोरी लीक नहीं होती।
  2. स्पष्टता: std::make_unique() इस्तेमाल करने से कोड ज़्यादा साफ और संक्षिप्त हो जाता है। इसमें यह बताने की ज़रूरत नहीं पड़ती कि कौन-सा टाइप आवंटित हो रहा है, क्योंकि टेम्पलेट आर्गुमेंट अपने-आप निकाल लिए जाते हैं।
  3. ऑप्टिमाइज़ेशन के मौके: new से खुद मेमोरी आवंटित करने की तुलना में कंपाइलर std::make_unique() को ज़्यादा बेहतर तरीके से ऑप्टिमाइज़ कर सकते हैं, जिससे परफॉर्मेंस बेहतर हो सकती है।
  4. गलत इस्तेमाल से बचाव: जब std::unique_ptr को हाथ से बनाया जाता है, तो मूल रिसोर्स को डिलीट करना संभव होता है। जब स्कोप खत्म होने पर std::unique_ptr उसे डिलीट करने की कोशिश करता है, तो इससे अपरिभाषित व्यवहार हो सकता है।

शेयर्ड पॉइंटर

std::shared_ptr एक स्मार्ट पॉइंटर है जो कई std::shared_ptr ऑब्जेक्ट को एक ही रिसोर्स का साझा स्वामित्व देता है। यह हिसाब रखता है कि कितने शेयर्ड पॉइंटर उस रिसोर्स को रेफर कर रहे हैं, और मेमोरी तभी मुक्त करता है जब रिसोर्स का मालिक अंतिम शेयर्ड पॉइंटर स्कोप से बाहर चला जाता है या रीसेट हो जाता है।

// Declaring and defining a shared pointer to a dynamically allocated string
auto martian_congressional_republic = std::make_shared<std::string>("protomolecule");

// Creating more shared pointer that shares ownership
auto outer_planets_alliance = martian_congressional_republic;
auto united_nations = martian_congressional_republic;
Caution

C++17 और उससे पहले के वर्ज़न में std::make_shared<T[]> के ज़रिए std::shared_ptr को ऐरे के साथ इस्तेमाल करना सीधे तौर पर समर्थित नहीं है। std::make_shared<T[]> से ऐरे आवंटित करना भले संभव हो, लेकिन उनसे सीधे शेयर्ड पॉइंटर बनाने पर अपरिभाषित व्यवहार हो सकता है, क्योंकि अकेले ऑब्जेक्ट और ऐरे की मेमोरी का प्रबंधन अलग-अलग तरीके से होता है। इसके बजाय ऐरे को शेयर्ड पॉइंटर के साथ ठीक से संभालने के लिए std::vector या कस्टम डिलीशन फंक्शन इस्तेमाल करने पर विचार कीजिए। C++17 में ऐरे आवंटन और शेयर्ड पॉइंटर के साथ काम करते समय अपने कंपाइलर और स्टैंडर्ड लाइब्रेरी के कार्यान्वयन के साथ संगतता हमेशा सुनिश्चित कीजिए।

std::make_shared() के फायदे

std::make_unique() की तरह std::make_shared() में भी बेहतर मेमोरी दक्षता, एक्सेप्शन सुरक्षा और पढ़ने में आसानी जैसे फायदे हैं। यह कंट्रोल ब्लॉक और मैनेज किए जाने वाले ऑब्जेक्ट, दोनों के लिए मेमोरी आवंटन एक ही ऑपरेशन में कर देता है, जिससे दक्षता बढ़ती है और मेमोरी लीक का खतरा घटता है। साथ ही, टेम्पलेट आर्गुमेंट अपने-आप निकल जाने से कोड सरल हो जाता है और उसे पढ़ना आसान हो जाता है। C++ में std::shared_ptr ऑब्जेक्ट के साथ काम करते समय std::make_shared() इस्तेमाल करने से कोड ज़्यादा साफ, ज़्यादा सुरक्षित और ज़्यादा दक्ष बनता है।

वीक पॉइंटर

std::weak_ptr, std::shared_ptr की साथी क्लास है। यह शेयर्ड पॉइंटर से मैनेज होने वाले किसी ऑब्जेक्ट के लिए एक ऐसा रेफरेंस देती है जो उसका मालिक नहीं होता, यानी एक "वीक" रेफरेंस। ऐसा रेफरेंस रिसोर्स तक पहुँच तो देता है, पर उसके जीवनकाल पर कोई असर नहीं डालता। वीक पॉइंटर उन जगहों पर काम आते हैं जहाँ मेमोरी लीक रोकने के लिए चक्रीय रेफरेंस तोड़ने पड़ते हैं।

// Creating a shared pointer
auto your_account = std::make_shared<std::string>("secret_subscription_password");
// Creating a shared pointer that shares ownership
auto your_flatmates_account = your_account;

// Creating a weak pointer from the shared pointer
auto your_flatmates_boyfriends_account = your_flatmates_account;
// if your_account and your_flatmates_account are deleted, there is no more reference to the shared pointer.
// your_flatmates_boyfriends_account will be a null pointer and cannot use the associated object any longer.

std::weak_ptr और चक्रीय स्वामित्व

std::shared_ptr इस्तेमाल करते समय कभी-कभी चक्रीय स्वामित्व की समस्या खड़ी हो जाती है, जिसे सर्कुलर रेफरेंस भी कहा जाता है। std::weak_ptr इसी समस्या से निपटने के लिए बनाया गया है।

चक्रीय स्वामित्व की स्थिति में दो या ज़्यादा std::shared_ptr ऑब्जेक्ट एक-दूसरे को रेफर कर रहे होते हैं। इससे एक चक्र बन जाता है और कोई भी ऑब्जेक्ट डिलीट नहीं हो पाता, क्योंकि हर ऑब्जेक्ट का दूसरे पर स्ट्रॉन्ग रेफरेंस बना रहता है। नतीजा यह होता है कि मेमोरी लीक हो जाती है।

std::weak_ptr इस समस्या का हल यह देता है कि वह शेयर्ड ऑब्जेक्ट के लिए वीक रेफरेंस बनने देता है, पर उसके रेफरेंस काउंट में नहीं जुड़ता। इसका मतलब है कि यह उस शेयर्ड ऑब्जेक्ट को देख भी सकता है और उस तक पहुँच भी सकता है, लेकिन उसके डिलीट होने को नहीं रोकता। अगर शेयर्ड ऑब्जेक्ट के सारे स्ट्रॉन्ग रेफरेंस हट जाएँ, तो ऑब्जेक्ट नष्ट हो जाता है और उसे देखने वाले वीक पॉइंटर अपने-आप nullptr पर रीसेट हो जाते हैं।

यहाँ एक छोटा उदाहरण है जो दिखाता है कि std::weak_ptr से चक्रीय स्वामित्व कैसे तोड़ा जाता है:

struct Node {
    Node() { std::cout << "Node constructed\n"; }
    ~Node() { std::cout << "Node destructed\n"; }

    std::weak_ptr<Node> next;
};

// Creating two nodes
auto node1 = std::make_shared<Node>();
auto node2 = std::make_shared<Node>();

// Creating a cycle by making each node point to the other
node1->next = node2;
node2->next = node1;

// Release strong references to nodes
node1.reset();
node2.reset();

// Nodes are automatically destructed due to weak references

"Node constructed" दो बार प्रिंट होता है, और उसके बाद "Node destructed" की दो लाइनें आती हैं।

जब next वीक पॉइंटर नहीं, बल्कि std::shared_ptr होता है, तो चक्रीय स्वामित्व की स्थिति बन जाती है। दोनों नोड एक-दूसरे पर स्ट्रॉन्ग रेफरेंस रखते हैं, इसलिए वे नष्ट नहीं हो पाते और मेमोरी लीक हो जाती है।

struct Node {
    Node() { std::cout << "Node constructed\n"; }
    ~Node() { std::cout << "Node destructed\n"; }

    std::shared_ptr<Node> next; // Change to shared_ptr
};

इस स्थिति में node1 और node2, दोनों std::shared_ptr के ज़रिए एक-दूसरे पर स्ट्रॉन्ग रेफरेंस रखते हैं और एक चक्र बना लेते हैं। जब प्रोग्राम खत्म होता है और reset() से ये स्ट्रॉन्ग रेफरेंस हटाने की कोशिश करता है, तब भी दोनों नोड के रेफरेंस काउंट शून्य नहीं होते, क्योंकि हर नोड का दूसरे पर स्ट्रॉन्ग रेफरेंस बना रहता है। इसलिए नोड नष्ट नहीं होते और मेमोरी लीक हो जाती है। "Node constructed" दो बार प्रिंट होता है, लेकिन उसके बाद "Node destructed" की कोई लाइन नहीं आती।

डैंगलिंग पॉइंटर और std::weak_ptr

डैंगलिंग पॉइंटर तब बनते हैं जब कोई पॉइंटर ऐसे ऑब्जेक्ट को रेफर करता है जिसे डिलीट किया जा चुका है। ऐसे पॉइंटर को डीरेफरेंस करने पर अपरिभाषित व्यवहार होता है। std::weak_ptr, std::shared_ptr से मैनेज होने वाले ऑब्जेक्ट के लिए मालिकी न रखने वाला वीक रेफरेंस देकर डैंगलिंग पॉइंटर से बचने में मदद करता है। std::weak_ptr की मदद से डैंगलिंग पॉइंटर से कैसे बचा जाए, यह देखिए:

  1. std::weak_ptr बनाइए: सीधे std::shared_ptr रखने की जगह शेयर्ड ऑब्जेक्ट के लिए एक std::weak_ptr बनाइए।

  2. वैधता जाँचिए: std::weak_ptr इस्तेमाल करने से पहले expired() फंक्शन से यह जाँच लीजिए कि वह वैध है या नहीं। अगर जुड़ा हुआ शेयर्ड ऑब्जेक्ट डिलीट हो चुका है, तो यह फंक्शन true लौटाता है।

  3. std::weak_ptr को लॉक कीजिए: शेयर्ड ऑब्जेक्ट तक सुरक्षित तरीके से पहुँचने के लिए lock() फंक्शन इस्तेमाल कीजिए। अगर ऑब्जेक्ट अभी वैध है, तो यह उसी ऑब्जेक्ट की ओर इशारा करने वाला std::shared_ptr लौटाता है। अगर ऑब्जेक्ट डिलीट हो चुका है, तो lock() एक खाली std::shared_ptr लौटाता है।

यहाँ एक कोड उदाहरण है जो दिखाता है कि std::weak_ptr से डैंगलिंग पॉइंटर कैसे टाले जाते हैं:

auto node1 = std::make_shared<Node>();
auto node2 = std::make_shared<Node>();

// Create weak pointers
node1->next = node2;
node2->next = node1;

// Check validity and lock
if (auto lockedNode2 = node1->next.lock()) {
    // Use lockedNode2 safely
} else {
    // Handle case where node2 has been deleted
}

इस उदाहरण में node1->next और node2->next, दोनों std::weak_ptr ऑब्जेक्ट हैं। शेयर्ड ऑब्जेक्ट तक पहुँचने से पहले expired() फंक्शन से उसकी वैधता जाँची जाती है। फिर सुरक्षित तरीके से उस शेयर्ड ऑब्जेक्ट तक पहुँचने के लिए lock() इस्तेमाल किया जाता है। यह तरीका पक्का करता है कि std::weak_ptr के साथ काम करते समय डैंगलिंग पॉइंटर से बचा जाए।

इस्तेमाल से जुड़ी सलाह

आम तौर पर स्मार्ट पॉइंटर ही इस्तेमाल कीजिए: अकेली मालिकी के लिए std::unique_ptr और साझा मालिकी के लिए std::shared_ptr। रॉ पॉइंटर उन जगहों के लिए बचाकर रखिए जहाँ रेफरेंस पर मालिकी नहीं चाहिए, या जहाँ पुराने कोड के साथ काम करना है। ज़्यादातर मामलों में अकेली मालिकी के लिए std::unique_ptr ही काफी है, क्योंकि यह हल्का-फुल्का मेमोरी प्रबंधन देता है और रेफरेंस काउंटिंग का बोझ नहीं डालता। std::shared_ptr कम ही इस्तेमाल कीजिए, क्योंकि इससे बोझ और जटिलता बढ़ती है, सिवाय उस स्थिति के जब सचमुच साझा मालिकी की ज़रूरत हो। std::weak_ptr खास तौर पर चक्रीय निर्भरताएँ तोड़ने या शेयर्ड ऑब्जेक्ट पर नज़र रखने के लिए है, पर इसका इस्तेमाल आम नहीं है।

GitHub के ज़रिए संपादित करें यह लिंक नई विंडो या टैब में खुलता है।

स्मार्ट पॉइंटर सीखिए