मैं कब किस तरह के सूचक का उपयोग करता हूं?


228

ठीक है, इसलिए पिछली बार जब मैंने जीवित रहने के लिए C ++ लिखा std::auto_ptrथा , तो सभी std lib उपलब्ध थे, और boost::shared_ptrसभी क्रोध थे। मैं वास्तव में प्रदान किए गए अन्य स्मार्ट पॉइंटर प्रकार बूस्ट में कभी नहीं देखा। मैं समझता हूं कि C ++ 11 अब प्रदान करता है कुछ प्रकार के बढ़ावा के साथ आया था, लेकिन उनमें से सभी नहीं।

तो क्या किसी को यह निर्धारित करने के लिए एक सरल एल्गोरिथ्म है कि किस स्मार्ट पॉइंटर का उपयोग करना है? अधिमानतः गूंगे बिंदुओं (जैसे कच्चे संकेत T*) और बाकी बूस्ट स्मार्ट संकेत के बारे में सलाह सहित । (कुछ इस तरह महान होगा)।



1
मैं वास्तव में उम्मीद कर रहा हूं कि कोई व्यक्ति इस एसटीएल चयन फ्लोचार्ट की तरह एक अच्छा काम फ्लोचार्ट लेकर आए ।
आलोक सेव

1
@ मोती: ओह, यह वास्तव में एक अच्छा एक है! मैंने इसे सामान्यीकृत किया।
sbi

6
@ डीडुलेटर वह डुप्लिकेट होने के करीब भी नहीं है। जुड़ा हुआ सवाल कहते हैं, "मैं का उपयोग करना चाहिए जब एक स्मार्ट सूचक" है और इस सवाल यह है कि "मैं प्रयोग करते हैं जब इन स्मार्ट संकेत दिए गए?" यानी यह मानक स्मार्ट पॉइंटर्स के विभिन्न उपयोगों को वर्गीकृत कर रहा है। जुड़ा हुआ प्रश्न ऐसा नहीं करता है। अंतर प्रतीत होता है छोटा है, लेकिन यह एक बड़ा है।
रप्त्ज

जवाबों:


183

साझा स्वामित्व: और मानक को अपनाया काफी उनके जैसे ही हैं बूस्ट समकक्षों । जब आप किसी संसाधन को साझा करने की आवश्यकता हो तो उनका उपयोग करें और पता नहीं कि कौन सा जीवित होने के लिए अंतिम होगा। अपने जीवनकाल को प्रभावित किए बिना साझा संसाधन का निरीक्षण करने के लिए उपयोग करें , न कि चक्रों को तोड़ने के लिए। साथ साइकिल सामान्य रूप से ऐसा नहीं होना चाहिए - दो संसाधनों एक दूसरे को ही नहीं कर सकते।
shared_ptrweak_ptrweak_ptrshared_ptr

ध्यान दें कि बूस्ट अतिरिक्त रूप से प्रदान करता है shared_array, जो एक उपयुक्त विकल्प हो सकता है shared_ptr<std::vector<T> const>

अगला, बूस्ट ऑफर intrusive_ptr, जो एक हल्का समाधान है यदि आपका संसाधन पहले से ही संदर्भ-गणना प्रबंधन प्रदान करता है और आप इसे RAII सिद्धांत को अपनाना चाहते हैं। इस एक को मानक द्वारा नहीं अपनाया गया था।

अद्वितीय स्वामित्व:
बूस्ट में भी एक है scoped_ptr, जो कि प्रतिलिपि बनाने योग्य नहीं है और जिसके लिए आप डेलेटर निर्दिष्ट नहीं कर सकते हैं। std::unique_ptrहै boost::scoped_ptrस्टेरॉयड पर और अपने होना चाहिए डिफ़ॉल्ट विकल्प है जब आप एक स्मार्ट सूचक की जरूरत है । यह आपको इसके टेम्पलेट तर्कों में एक निपुण निर्दिष्ट करने की अनुमति देता है और इसके विपरीत, जंगम हैboost::scoped_ptr । यह एसटीएल कंटेनरों में भी पूरी तरह से प्रयोग करने योग्य है, जब तक कि आप उन ऑपरेशनों का उपयोग नहीं करते हैं, जिन्हें कॉपी करने योग्य प्रकार (स्पष्ट रूप से) की आवश्यकता होती है।

फिर से ध्यान दें, कि बूस्ट का एक सरणी संस्करण है: scoped_arrayजो std::unique_ptr<T[]>कि आंशिक विशेषज्ञता की आवश्यकता के द्वारा एकीकृत होता है delete[]जो सूचक को deleteआईएनजी के बजाय इसे ( default_deleteआर के साथ ) करेगा। std::unique_ptr<T[]>यह भी प्रदान करता है operator[]के बजाय operator*और operator->

ध्यान दें कि std::auto_ptrअभी भी मानक में है, लेकिन यह पदावनत है§D.10 [depr.auto.ptr]

वर्ग टेम्पलेट auto_ptrको हटा दिया गया है। [ नोट: वर्ग टेम्पलेट unique_ptr(२०. ).१) एक बेहतर समाधान प्रदान करता है। ध्यान दें ]

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

यदि आप किसी संसाधन के लिए एक गैर-स्वामित्व संदर्भ चाहते हैं, लेकिन आप नहीं जानते कि संसाधन उस ऑब्जेक्ट को संदर्भित करेगा जो इसे संदर्भित करता है, तो संसाधन को एक में पैक करें shared_ptrऔर उपयोग करें weak_ptr- यदि आप माता-पिता के shared_ptrसाथ जीवित हैं तो आप परीक्षण कर सकते हैं lock, जो होगा एक वापसी shared_ptrहै कि गैर-शून्य है, तो संसाधन अभी भी मौजूद है। यदि परीक्षण करना चाहते हैं कि संसाधन मृत है, तो उपयोग करें expired। दोनों समान लग सकते हैं, लेकिन समवर्ती निष्पादन के चेहरे में बहुत भिन्न हैं, क्योंकि expiredकेवल उस एकल विवरण के लिए इसके वापसी मूल्य की गारंटी देता है। लगता है जैसे निर्दोष परीक्षण

if(!wptr.expired())
  something_assuming_the_resource_is_still_alive();

एक संभावित दौड़ की स्थिति है।


1
बिना किसी स्वामित्व के मामले में, आपको संभवतः पॉइंटर्स के संदर्भों को प्राथमिकता देना चाहिए जब तक कि आपको कोई स्वामित्व और रीसेट करने की आवश्यकता न हो, जहां संदर्भों में कटौती नहीं होगी, तब भी आप मूल वस्तु को फिर से लिखने के लिए विचार कर सकते हैं shared_ptrऔर न होने वाला सूचक a weak_ptr...
डेविड रॉड्रिग्ज - drieaseas

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

1
@ डेविड: आह, मैं देख रहा हूं। :) हाँ, संदर्भ इसके लिए बुरे नहीं हैं, मैं व्यक्तिगत रूप से उन्हें ऐसे मामलों में भी पसंद करता हूं। मैं उन्हें जोड़ दूंगा।
Xio

1
@ Xeo: नहीं करने के shared_array<T>लिए एक विकल्प shared_ptr<T[]>है shared_ptr<vector<T>>: यह नहीं बढ़ सकता है।
आर। मार्टिनो फर्नांडीस

1
@GregroyCurrie: यह ... बिल्कुल वही है जो मैंने लिखा है? मैंने कहा कि यह संभावित दौड़ की स्थिति का एक उदाहरण है।
Xeo

127

निर्णय लेना कि स्मार्ट पॉइंटर का उपयोग करना स्वामित्व का सवाल है । जब यह संसाधन प्रबंधन की बात आती है, तो वस्तु ए, वस्तु बी का मालिक है यदि यह वस्तु बी के जीवनकाल के नियंत्रण में है। उदाहरण के लिए, सदस्य चर का स्वामित्व उनकी संबंधित वस्तुओं के पास होता है क्योंकि सदस्य चर का जीवनकाल वस्तु के जीवनकाल से बंधा होता है। आप स्मार्ट पॉइंटर्स चुनते हैं जो ऑब्जेक्ट के स्वामित्व में है।

ध्यान दें कि सॉफ़्टवेयर सिस्टम में स्वामित्व स्वामित्व से अलग है क्योंकि हम सॉफ़्टवेयर के बाहर इसके बारे में सोचेंगे। उदाहरण के लिए, एक व्यक्ति अपने घर को "खुद" कर सकता है, लेकिन इसका मतलब यह नहीं है कि किसी Personवस्तु का जीवनकाल पर नियंत्रण होता है House। सॉफ्टवेयर अवधारणाओं के साथ इन वास्तविक दुनिया की अवधारणाओं को जोड़ना अपने आप को एक छेद में प्रोग्राम करने का एक निश्चित तरीका है।


यदि आपके पास ऑब्जेक्ट का एकमात्र स्वामित्व है, तो उपयोग करें std::unique_ptr<T>

यदि आपने वस्तु का स्वामित्व साझा किया है ...
- यदि स्वामित्व में कोई चक्र नहीं हैं, तो उपयोग करें std::shared_ptr<T>
- यदि चक्र हैं, तो एक "दिशा" को परिभाषित करें std::shared_ptr<T>और एक दिशा std::weak_ptr<T>में और दूसरे में उपयोग करें ।

यदि ऑब्जेक्ट आपके पास है, लेकिन कोई मालिक नहीं होने की संभावना है, तो सामान्य पॉइंटर्स T*(जैसे पैरेंट पॉइंटर्स) का उपयोग करें।

यदि ऑब्जेक्ट आपके पास है (या अन्यथा अस्तित्व की गारंटी है), तो संदर्भ का उपयोग करें T&


कैविएट: स्मार्ट पॉइंटर्स की लागतों से अवगत रहें। स्मृति या प्रदर्शन सीमित वातावरण में, स्मृति के प्रबंधन के लिए अधिक मैनुअल योजना के साथ सामान्य बिंदुओं का उपयोग करना फायदेमंद हो सकता है।

मूल्य:

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

उदाहरण:

struct BinaryTree
{
    Tree* m_parent;
    std::unique_ptr<BinaryTree> m_children[2]; // or use std::array...
};

एक द्विआधारी वृक्ष का अपना माता-पिता नहीं होता है, लेकिन एक पेड़ का अस्तित्व उसके माता-पिता (या nullptrजड़ के लिए) के अस्तित्व को दर्शाता है , जिससे कि एक सामान्य सूचक का उपयोग किया जाता है। एक बाइनरी ट्री (मूल्य शब्दार्थ के साथ) अपने बच्चों का एकमात्र स्वामित्व है, इसलिए वे हैं std::unique_ptr

struct ListNode
{
    std::shared_ptr<ListNode> m_next;
    std::weak_ptr<ListNode> m_prev;
};

यहां, सूची नोड अपनी अगली और पिछली सूचियों का स्वामी है, इसलिए हम चक्र को तोड़ने के लिए एक दिशा और shared_ptrअगले के लिए उपयोग weak_ptrकरते हैं।


3
बाइनरी ट्री के उदाहरण के लिए कुछ लोग shared_ptr<BinaryTree>बच्चों के weak_ptr<BinaryTree>लिए और माता-पिता के रिश्ते के लिए उपयोग करने का सुझाव देंगे ।
डेविड रॉड्रिग्ज़ - drieaseas

@ DavidRodríguez-dribeas: यह निर्भर करता है कि ट्री का मूल्य शब्दार्थ है या नहीं। यदि स्रोत वृक्ष के नष्ट होने पर भी लोग आपके पेड़ को बाहरी रूप से संदर्भित करते हैं, तो हाँ, साझा / कमजोर सूचक कॉम्बो सबसे अच्छा होगा।
पीटर अलेक्जेंडर

यदि कोई वस्तु आपके पास है और अस्तित्व की गारंटी है तो संदर्भ क्यों नहीं।
मार्टिन यॉर्क

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

3
+1 लेकिन आपको पहली पंक्ति में "स्वामित्व" की परिभाषा जोड़नी चाहिए। मैं अक्सर खुद को स्पष्ट रूप से बताता हूं कि यह वस्तु के जीवन और मृत्यु के बारे में है, न कि अधिक डोमेन-विशिष्ट अर्थ में स्वामित्व।
३०

19

unique_ptr<T>जब आपको संदर्भ गणना की आवश्यकता हो, तब सभी समय का उपयोग करें , जिसमें केस का उपयोग करें shared_ptr<T>(और बहुत दुर्लभ मामलों के लिए, weak_ptr<T>संदर्भ चक्र को रोकने के लिए)। लगभग हर मामले में, हस्तांतरणीय अद्वितीय स्वामित्व ठीक है।

रॉ पॉइंटर्स: अच्छा तभी होगा जब आपको कोवरिएंट रिटर्न, नॉन-ओनिंग पॉइंटिंग की आवश्यकता हो जो कि हो सकता है। वे अन्यथा उपयोगी नहीं हैं।

ऐरे पॉइंटर्स: unique_ptrएक विशेषज्ञता है T[]जिसके लिए स्वचालित रूप delete[]से परिणाम पर कॉल किया जाता है, इसलिए आप unique_ptr<int[]> p(new int[42]);उदाहरण के लिए सुरक्षित रूप से कर सकते हैं । shared_ptrआपको अभी भी एक कस्टम डीलेटर की आवश्यकता होगी, लेकिन आपको एक विशेष साझा या अद्वितीय सरणी सूचक की आवश्यकता नहीं होगी। बेशक, ऐसी चीजें आमतौर पर std::vectorवैसे भी सबसे अच्छी तरह से प्रतिस्थापित की जाती हैं । दुर्भाग्य से shared_ptrएक ऐरे एक्सेस फंक्शन प्रदान नहीं करता है, इसलिए आपको अभी भी मैन्युअल रूप से कॉल करना होगा get(), लेकिन इसके बजाय और unique_ptr<T[]>प्रदान करता है । किसी भी मामले में, आपको खुद को जांचना होगा। यह थोड़ा कम उपयोगकर्ता के अनुकूल बनाता है , हालांकि निश्चित रूप से सामान्य लाभ और कोई बूस्ट निर्भरता और विजेताओं को फिर से नहीं बनाता है ।operator[]operator*operator->shared_ptrunique_ptrshared_ptr

स्कोपेड पॉइंटर्स: unique_ptrजैसे कि अप्रासंगिक बना दिया auto_ptr

वास्तव में इससे ज्यादा कुछ नहीं है। C ++ 03 में मूवमेंट शब्दार्थ के बिना यह स्थिति बहुत जटिल थी, लेकिन C ++ 11 में सलाह बहुत सरल है।

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


इसके अलावा, पुनरावृत्ति के लिए कच्चे संकेत। और आउटपुट पैरामीटर बफ़र्स के लिए, जहां बफर कॉलर के स्वामित्व में है।
बेन वोइगट

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

2
std::unique_ptr<T[]>प्रदान करता है operator[]के बजाय operator*और operator->। यह सच है कि आपको अभी भी स्वयं की जाँच करने की आवश्यकता है।
Xeo

8

उपयोग करने के मामले के मामले unique_ptr:

  • कारखाने के तरीके
  • सदस्य जो संकेत हैं (पिंपल शामिल)
  • Stl नियंत्रक में संचयक (चाल से बचने के लिए)
  • बड़ी स्थानीय गतिशील वस्तुओं का उपयोग

उपयोग करने के मामले के मामले shared_ptr:

  • धागे के पार वस्तुओं को साझा करना
  • सामान्य रूप से वस्तुओं को साझा करना

उपयोग करने के मामले के मामले weak_ptr:

  • बड़ा नक्शा जो सामान्य संदर्भ के रूप में कार्य करता है (उदा। सभी खुले सॉकेट का मानचित्र)

बेझिझक संपादित करें और अधिक जोड़ें


जैसा कि आप परिदृश्य देते हैं मैं वास्तव में आपके उत्तर को अधिक पसंद करता हूं।
निकोलस हम्फ्री
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.