मुझे उन ऑब्जेक्ट्स के आस-पास पासिंग पॉइंट्स के विभिन्न व्यवहार्य मोड्स को बताने की कोशिश करें, जिनकी मेमोरी को std::unique_ptrक्लास टेम्पलेट के एक उदाहरण द्वारा प्रबंधित किया जाता है ; यह पुराने std::auto_ptrवर्ग के टेम्पलेट पर भी लागू होता है (जो मुझे लगता है कि सभी यूनिक पॉइंटर का उपयोग करने की अनुमति देता है, लेकिन इसके लिए संशोधित रूपांतरों को स्वीकार किया जाएगा, जहां प्रतिद्वंद्वियों की अपेक्षा की जाती है, बिना आह्वान किए std::move), और कुछ हद तक भी std::shared_ptr।
चर्चा के लिए एक ठोस उदाहरण के रूप में मैं निम्नलिखित सरल सूची प्रकार पर विचार करूंगा
struct node;
typedef std::unique_ptr<node> list;
struct node { int entry; list next; }
ऐसी सूची के उदाहरण (जिन्हें अन्य उदाहरणों के साथ भागों को साझा करने या परिपत्र होने की अनुमति नहीं दी जा सकती है) पूरी तरह से किसी के पास है जो प्रारंभिक listसंकेतक रखता है । यदि क्लाइंट कोड जानता है कि यह जो सूची संग्रहीत करता है, वह कभी खाली नहीं होगी, तो यह nodeसीधे a के बजाय पहले स्टोर करना चुन सकता है list। nodeपरिभाषित किए जाने की आवश्यकता के लिए कोई विध्वंसक नहीं है : चूंकि इसके क्षेत्रों के लिए विनाशकों को स्वचालित रूप से कहा जाता है, इसलिए पूरी सूची को प्रारंभिक सूचक या नोड के जीवनकाल समाप्त होने पर स्मार्ट पॉइंटर विध्वंसक द्वारा पुन: हटा दिया जाएगा।
यह पुनरावर्ती प्रकार कुछ मामलों पर चर्चा करने का अवसर देता है जो स्मार्ट सूचक के सादे डेटा के मामले में कम दिखाई देते हैं। साथ ही फ़ंक्शन स्वयं कभी-कभी क्लाइंट कोड का एक उदाहरण (पुनरावर्ती) प्रदान करते हैं। इसके लिए टाइप किए गए listपाठ्यक्रम की ओर से पक्षपाती है unique_ptr, लेकिन परिभाषा का उपयोग करने के लिए auto_ptrया shared_ptrइसके बजाय बदलने के लिए बदला जा सकता है, जो नीचे कहा गया है (जो कि विध्वंसकों को लिखने की आवश्यकता के बिना अपवाद सुरक्षा के बारे में निश्चित रूप से आश्वस्त किए बिना बदले जाने की आवश्यकता है)।
चारों ओर स्मार्ट पॉइंटर्स पास करने के मोड
मोड 0: स्मार्ट पॉइंटर के बजाय एक पॉइंटर या संदर्भ तर्क पास करें
यदि आपका कार्य स्वामित्व से संबंधित नहीं है, तो यह पसंदीदा तरीका है: इसे बिल्कुल स्मार्ट पॉइंटर न बनाएं। इस मामले में, आपके फ़ंक्शन को यह चिंता करने की आवश्यकता नहीं है कि इंगित की गई वस्तु का मालिक कौन है, या इसका क्या मतलब है कि स्वामित्व प्रबंधित है, इसलिए एक कच्चा सूचक पास करना पूरी तरह से सुरक्षित है, और सबसे लचीला रूप है, क्योंकि मालिक की परवाह किए बिना हमेशा ग्राहक हो सकता है एक कच्चे सूचक का उत्पादन करें (या तो getविधि को बुलाकर या ऑपरेटर के पते से &)।
उदाहरण के लिए, इस तरह की सूची की लंबाई की गणना करने के लिए, एक listतर्क नहीं दिया जाना चाहिए , लेकिन एक कच्चा सूचक:
size_t length(const node* p)
{ size_t l=0; for ( ; p!=nullptr; p=p->next.get()) ++l; return l; }
एक क्लाइंट जो एक वैरिएबल रखता है , list headइस फ़ंक्शन को कॉल कर सकता है length(head.get()), जबकि एक क्लाइंट जो node nएक गैर-खाली सूची का प्रतिनिधित्व करने के बजाय चुन सकता है, कॉल कर सकता है length(&n)।
यदि पॉइंटर को नॉन नल होने की गारंटी दी जाती है (जो कि लिस्ट खाली होने के बाद भी यहां नहीं है) तो कोई पॉइंटर के बजाय रेफरेंस पास करना पसंद कर सकता है। यह गैर-के लिए एक पॉइंटर / संदर्भ हो सकता है - constयदि फ़ंक्शन को नोड (एस) की सामग्री को अपडेट करने की आवश्यकता है, तो उनमें से किसी को जोड़ने या हटाने के बिना (बाद में स्वामित्व शामिल होगा)।
एक दिलचस्प मामला जो मोड 0 श्रेणी में आता है वह सूची की एक (गहरी) प्रतिलिपि बना रहा है; हालांकि ऐसा करने वाले किसी फ़ंक्शन को उस प्रतिलिपि के स्वामित्व के स्वामित्व को हस्तांतरित करना चाहिए, जो उस प्रतिलिपि के सूची के स्वामित्व से संबंधित नहीं है। तो इसे निम्नानुसार परिभाषित किया जा सकता है:
list copy(const node* p)
{ return list( p==nullptr ? nullptr : new node{p->entry,copy(p->next.get())} ); }
इस कोड को योग्यता के आधार एक करीबी नजर, दोनों कारण है कि यह सब पर (पुनरावर्ती कॉल के परिणाम के संकलित करने के लिए के रूप में प्रश्न के लिए copyinitialiser सूची बांध में की चाल निर्माता में rvalue संदर्भ तर्क के लिए unique_ptr<node>, उर्फ list, जब initialising nextके क्षेत्र उत्पन्न node), और इस सवाल के लिए कि यह अपवाद-सुरक्षित क्यों है (यदि पुनरावर्ती आवंटन प्रक्रिया के दौरान मेमोरी समाप्त हो जाती है और कुछ कॉल newफेंकता है std::bad_alloc, तो उस समय आंशिक रूप से निर्मित सूची के लिए एक सूचक को अस्थायी रूप से गुमनाम रूप से रखा जाता है listप्रारंभिक सूची के लिए बनाया गया है, और इसका विध्वंसक उस आंशिक सूची को साफ करेगा)। वैसे एक को बदलने के लिए प्रलोभन का विरोध करना चाहिए (जैसा कि मैंने शुरू में किया था) दूसरा nullptrद्वाराp, जो सब के बाद उस बिंदु पर शून्य होने के लिए जाना जाता है: कोई भी एक कच्चे (कच्चे) पॉइंटर से स्मार्ट पॉइंटर का निर्माण नहीं कर सकता है , यहां तक कि जब यह अशक्त होने के लिए जाना जाता है।
मोड 1: मूल्य से एक स्मार्ट पॉइंटर पास करें
एक फ़ंक्शन जो तर्क के रूप में एक स्मार्ट पॉइंटर वैल्यू लेता है, ठीक उसी ओर इंगित की गई वस्तु के कब्जे में आता है: स्मार्ट पॉइंटर जिसे आयोजित किया जाता है (चाहे नामांकित चर या अनाम अस्थायी में) फ़ंक्शन के प्रवेश द्वार पर लॉजिक मान में कॉपी किया जाता है और कॉल करने वाला सूचक अशक्त हो गया है (एक अस्थायी के मामले में प्रतिलिपि को हटा दिया गया हो सकता है, लेकिन किसी भी मामले में कॉलर को इंगित ऑब्जेक्ट तक पहुंच खो दी है)। मैं कैश द्वारा इस मोड कॉल को कॉल करना चाहूंगा : कॉल करने वाली सेवा के लिए कॉलर भुगतान करता है, और कॉल के बाद स्वामित्व के बारे में कोई भ्रम नहीं हो सकता है। यह स्पष्ट करने के लिए, भाषा के नियमों में तर्क को लपेटने के लिए कॉलर की आवश्यकता होती हैstd::moveयदि स्मार्ट पॉइंटर को एक चर में रखा जाता है (तकनीकी रूप से, यदि तर्क एक अंतराल है); इस मामले में (लेकिन नीचे मोड 3 के लिए नहीं) यह फ़ंक्शन वही करता है जो उसका नाम बताता है, अर्थात् चर से मान को अस्थायी रूप में स्थानांतरित करता है, चर शून्य को छोड़कर।
ऐसे मामलों के लिए, जहां कहा जाने वाला फ़ंक्शन बिना शर्त के (पायलटों) पॉइंट-टू-ऑब्जेक्ट का स्वामित्व लेता है, इस मोड का उपयोग std::unique_ptrया std::auto_ptrइसके स्वामित्व के साथ एक पॉइंटर पास करने का एक अच्छा तरीका है, जो मेमोरी लीक के किसी भी जोखिम से बचा जाता है। फिर भी मुझे लगता है कि ऐसी बहुत कम स्थितियाँ हैं जहाँ मोड 3 नीचे पसंद नहीं किया जाना है (कभी-कभार थोड़ा) 1 मोड पर। इस कारण से मैं इस मोड का कोई उपयोग उदाहरण प्रदान नहीं करूँगा। (लेकिन reversedनीचे दिए गए मोड 3 का उदाहरण देखें, जहां यह टिप्पणी की गई है कि मोड 1 कम से कम उतना ही अच्छा करेगा।) यदि फ़ंक्शन केवल इस सूचक की तुलना में अधिक तर्क लेता है, तो ऐसा हो सकता है कि मोड से बचने के लिए तकनीकी कारण के अतिरिक्त है। 1 (साथ std::unique_ptrया std::auto_ptr): चूंकि पॉइंटर वैरिएबल पास करते समय एक वास्तविक चाल ऑपरेशन होता हैpअभिव्यक्ति द्वारा std::move(p), यह नहीं माना जा सकता है कि pअन्य तर्कों (मूल्यांकन के क्रम अनिर्दिष्ट) का मूल्यांकन करते समय एक उपयोगी मूल्य रखता है, जिससे सूक्ष्म त्रुटियां हो सकती हैं; इसके विपरीत, मोड 3 का उपयोग यह आश्वासन देता है कि pफ़ंक्शन कॉल से पहले कोई भी कदम नहीं उठाता है, इसलिए अन्य तर्क सुरक्षित रूप से एक मूल्य तक पहुंच सकते हैं p।
जब इसका उपयोग किया जाता है std::shared_ptr, तो यह मोड दिलचस्प होता है कि एकल फ़ंक्शन परिभाषा के साथ यह कॉलर को यह चुनने की अनुमति देता है कि फ़ंक्शन द्वारा उपयोग की जाने वाली एक नई साझाकरण प्रतिलिपि बनाते समय सूचक की एक साझा प्रतिलिपि अपने लिए रखनी है या नहीं (यह तब होता है जब एक लेवल्यू होता है तर्क प्रदान किया जाता है; कॉल पर उपयोग किए जाने वाले साझा पॉइंटर्स के लिए कॉपी कंस्ट्रक्टर संदर्भ संख्या को बढ़ाता है), या फ़ंक्शन को केवल एक को बनाए रखने या संदर्भ गणना को छूने के बिना सूचक की एक प्रति देने के लिए (यह तब होता है जब एक तर्क तर्क प्रदान किया जाता है, संभवतः एक आह्वान std::move) उदाहरण के लिए
void f(std::shared_ptr<X> x) // call by shared cash
{ container.insert(std::move(x)); } // store shared pointer in container
void client()
{ std::shared_ptr<X> p = std::make_shared<X>(args);
f(p); // lvalue argument; store pointer in container but keep a copy
f(std::make_shared<X>(args)); // prvalue argument; fresh pointer is just stored away
f(std::move(p)); // xvalue argument; p is transferred to container and left null
}
समान को अलग-अलग परिभाषित करने void f(const std::shared_ptr<X>& x)(लैवल्यू केस के लिए) और void f(std::shared_ptr<X>&& x)( रेवल्यू केस के लिए) द्वारा प्राप्त किया जा सकता है , जिसमें फ़ंक्शन बॉडीज केवल उस में भिन्न होती हैं, पहला संस्करण कॉपी सिमेंटिक्स का उपयोग करता है (उपयोग करते समय कॉपी निर्माण / असाइनमेंट का उपयोग करता है x), लेकिन दूसरा संस्करण शब्दार्थ ( std::move(x)इसके बजाय, उदाहरण कोड में लेखन )। इसलिए साझा किए गए बिंदुओं के लिए, कुछ कोड दोहराव से बचने के लिए मोड 1 उपयोगी हो सकता है।
मोड 2: (सूचक) लैवल्यू संदर्भ द्वारा एक स्मार्ट पॉइंटर पास करें
यहां फ़ंक्शन को केवल स्मार्ट पॉइंटर के लिए एक परिवर्तनीय संदर्भ होने की आवश्यकता होती है, लेकिन यह इसके साथ क्या करेगा इसका कोई संकेत नहीं देता है। मैं कार्ड द्वारा इस पद्धति को कॉल करना चाहूंगा : कॉलर क्रेडिट कार्ड नंबर देकर भुगतान सुनिश्चित करता है। संदर्भ का उपयोग पॉइंट-टू-ऑब्जेक्ट के स्वामित्व को लेने के लिए किया जा सकता है, लेकिन इसके पास नहीं है। इस मोड में एक संशोधित अंतराल तर्क प्रदान करने की आवश्यकता होती है, इस तथ्य के अनुसार कि फ़ंक्शन के वांछित प्रभाव में तर्क चर में एक उपयोगी मूल्य छोड़ना शामिल हो सकता है। एक कॉल राइवल अभिव्यक्ति के साथ जो इस तरह के फ़ंक्शन को पास करना चाहता है, उसे कॉल करने में सक्षम होने के लिए नामांकित चर में संग्रहीत करने के लिए मजबूर किया जाएगा, क्योंकि भाषा केवल स्थिरांक को अंतर्निहित रूपांतरण प्रदान करती हैलवल्यू संदर्भ (एक अस्थायी का संदर्भ देते हुए) एक लय से। (विपरीत स्थिति से संभाला के विपरीत std::move, से एक डाली Y&&को Y&, साथ Yस्मार्ट सूचक प्रकार, संभव नहीं है; फिर भी इस रूपांतरण एक साधारण टेम्पलेट समारोह यदि वास्तव में वांछित द्वारा प्राप्त किया जा सकता है, को देखने के https://stackoverflow.com/a/24868376 / 1436796 )। उस मामले के लिए जहां कहा जाता है कि फ़ंक्शन बिना शर्त के स्वामित्व का इरादा रखता है, तर्क से चोरी करते हुए, एक तर्क तर्क प्रदान करने का दायित्व गलत संकेत दे रहा है: कॉल के बाद चर का कोई उपयोगी मूल्य नहीं होगा। इसलिए मोड 3, जो हमारे फ़ंक्शन के अंदर समान संभावनाएं देता है, लेकिन कॉल करने वालों को एक प्रतिद्वंद्विता प्रदान करने के लिए कहता है, इस तरह के उपयोग के लिए प्राथमिकता दी जानी चाहिए।
हालाँकि मोड 2 के लिए एक वैध उपयोग मामला है, अर्थात् फ़ंक्शंस जो सूचक को संशोधित कर सकता है , या उस ऑब्जेक्ट को इंगित किया जा सकता है जिसमें स्वामित्व शामिल है । उदाहरण के लिए, एक फ़ंक्शन जो नोड को उपसर्ग listकरता है, ऐसे उपयोग का एक उदाहरण प्रदान करता है:
void prepend (int x, list& l) { l = list( new node{ x, std::move(l)} ); }
स्पष्ट रूप से कॉलर्स को उपयोग करने के लिए मजबूर करना यहां अवांछनीय होगा std::move, क्योंकि उनके स्मार्ट पॉइंटर अभी भी कॉल के बाद एक अच्छी तरह से परिभाषित और गैर-खाली सूची के मालिक हैं, हालांकि पहले की तुलना में एक अलग है।
फिर से यह देखना दिलचस्प है कि prependमुफ्त मेमोरी की कमी के लिए कॉल विफल होने पर क्या होता है । फिर newकॉल फेंक देगा std::bad_alloc; इस समय पर, चूंकि कोई nodeभी आवंटित नहीं किया जा सकता है, यह निश्चित है कि पास किए गए रेवल्यू रेफरेंस (मोड 3) को std::move(l)अभी तक पायलट नहीं किया जा सकता है, क्योंकि आवंटित किए जाने वाले nextक्षेत्र का निर्माण nodeकरने के लिए किया जाएगा। इसलिए मूल स्मार्ट पॉइंटर lअभी भी मूल सूची रखता है जब त्रुटि डाली जाती है; उस सूची को या तो स्मार्ट पॉइंटर डिस्ट्रक्टर द्वारा ठीक से नष्ट कर दिया जाएगा, या मामले में lपर्याप्त रूप से शुरुआती catchक्लॉज के लिए धन्यवाद से बच जाना चाहिए , यह अभी भी मूल सूची को रखेगा।
यह एक रचनात्मक उदाहरण था; इस प्रश्न पर एक पलक के साथ कोई भी दिए गए मान वाले पहले नोड को हटाने का अधिक विनाशकारी उदाहरण दे सकता है, यदि कोई हो:
void remove_first(int x, list& l)
{ list* p = &l;
while ((*p).get()!=nullptr and (*p)->entry!=x)
p = &(*p)->next;
if ((*p).get()!=nullptr)
(*p).reset((*p)->next.release()); // or equivalent: *p = std::move((*p)->next);
}
फिर से शुद्धता यहाँ काफी सूक्ष्म है। विशेष रूप से, अंतिम कथन में, (*p)->nextहटाए जाने के लिए नोड के अंदर रखे गए पॉइंटर को अनलिंक किया जाता है (द्वारा release, जो पॉइंटर लौटाता है, लेकिन मूल अशक्त बनाता है) इससे पहले reset कि उस नोड को नष्ट कर देता है (स्पष्ट रूप से) पुराने नोड को नष्ट कर देता है p), यह सुनिश्चित करता है एक और केवल एक नोड उस समय नष्ट हो जाता है। (टिप्पणी में उल्लिखित वैकल्पिक रूप में, इस समय को std::unique_ptrउदाहरण के चाल-असाइनमेंट के कार्यान्वयन के आंतरिक listबिंदुओं पर छोड़ दिया जाएगा , मानक 20.7.1.2.3 कहता है; 2 कि यह ऑपरेटर "के रूप में मानो" कॉलिंग reset(u.release())", जहां समय भी यहां सुरक्षित होना चाहिए।"
ध्यान दें कि prependऔर remove_firstउन ग्राहकों द्वारा नहीं बुलाया जा सकता है जो nodeहमेशा एक गैर-रिक्त सूची के लिए एक स्थानीय चर संग्रहित करते हैं , और ठीक ही इसलिए कि दिए गए कार्यान्वयन ऐसे मामलों के लिए काम नहीं कर सके।
मोड 3: एक स्मार्ट पॉइंटर को पास करें (मोडिफायेबल) रेवल्यू रेफरेंस द्वारा
यह केवल सूचक के स्वामित्व को लेते समय उपयोग करने के लिए पसंदीदा मोड है। मैं चेक द्वारा इस विधि को कॉल करना चाहूंगा : कॉल करने वाले को स्वामित्व को त्यागना चाहिए, जैसे कि चेक प्रदान करके, नकद पर हस्ताक्षर करके, लेकिन वास्तविक निकासी तब तक के लिए स्थगित कर दी जाती है जब तक कि फ़ंक्शन वास्तव में पॉइंटर को स्थानांतरित नहीं करता है (ठीक वैसे ही जब यह मोड 2 होगा )। "चेक पर हस्ताक्षर" का मतलब है कि कॉल करने वालों को तर्क में लपेटना पड़ता है std::move(जैसे कि मोड 1 में) यदि यह एक अंतराल है (यदि यह एक प्रतिद्वंद्विता है, तो "स्वामित्व छोड़ देना" हिस्सा स्पष्ट है और इसके लिए अलग कोड की आवश्यकता नहीं है)।
ध्यान दें कि तकनीकी रूप से मोड 3 बिल्कुल मोड 2 के रूप में व्यवहार करता है, इसलिए तथाकथित फ़ंक्शन को स्वामित्व मानने की आवश्यकता नहीं है ; हालाँकि मैं इस बात पर ज़ोर देना चाहूंगा कि यदि स्वामित्व हस्तांतरण (सामान्य उपयोग में) के बारे में कोई अनिश्चितता है, तो मोड 2 को मोड 3 के लिए प्राथमिकता दी जानी चाहिए, ताकि मोड 3 का उपयोग करना फोन करने वालों के लिए एक संकेत है कि वे स्वामित्व दे रहे हैं । कोई यह प्रतिशोध कर सकता है कि केवल मोड 1 तर्क वास्तव में संकेतों को कॉल करने वालों को स्वामित्व के नुकसान को मजबूर करता है। लेकिन अगर किसी ग्राहक को कॉल किए गए फ़ंक्शन के इरादों के बारे में कोई संदेह है, तो उसे फ़ंक्शन के विनिर्देशों को जानना चाहिए, जिसे उसके संदेह को दूर करना चाहिए।
हमारे listप्रकार को शामिल करने वाले एक विशिष्ट उदाहरण को खोजना आश्चर्यजनक रूप से कठिन है जो मोड 3 तर्क पासिंग का उपयोग करता है। एक सूची bको दूसरी सूची के अंत में ले aजाना एक विशिष्ट उदाहरण है; हालांकि a(जो बच जाता है और ऑपरेशन का परिणाम रखता है) मोड 2 का उपयोग करके बेहतर तरीके से पारित हो जाता है:
void append (list& a, list&& b)
{ list* p=&a;
while ((*p).get()!=nullptr) // find end of list a
p=&(*p)->next;
*p = std::move(b); // attach b; the variable b relinquishes ownership here
}
मोड 3 तर्क पासिंग का एक शुद्ध उदाहरण निम्नलिखित है जो एक सूची (और उसके स्वामित्व) को लेता है, और रिवर्स क्रम में समान नोड्स वाली सूची देता है।
list reversed (list&& l) noexcept // pilfering reversal of list
{ list p(l.release()); // move list into temporary for traversal
list result(nullptr);
while (p.get()!=nullptr)
{ // permute: result --> p->next --> p --> (cycle to result)
result.swap(p->next);
result.swap(p);
}
return result;
}
इस फ़ंक्शन l = reversed(std::move(l));को सूची को स्वयं में उलटने के लिए कहा जा सकता है, लेकिन उलट सूची का उपयोग अलग-अलग तरीके से भी किया जा सकता है।
यहां तर्क को तुरंत दक्षता के लिए एक स्थानीय चर में ले जाया जाता है (एक व्यक्ति पैरामीटर को lसीधे जगह में इस्तेमाल कर सकता है p, लेकिन फिर हर बार इसे एक्सेस करने से एक अतिरिक्त स्तर अप्रत्यक्ष हो जाएगा); इसलिए मोड 1 तर्क पासिंग के साथ अंतर न्यूनतम है। वास्तव में उस मोड का उपयोग करते हुए, तर्क सीधे स्थानीय चर के रूप में कार्य कर सकता था, इस प्रकार उस प्रारंभिक कदम से बचा जा सकता है; यह सामान्य सिद्धांत का एक उदाहरण है कि यदि संदर्भ द्वारा पारित एक तर्क केवल एक स्थानीय चर को आरंभ करने के लिए कार्य करता है, तो कोई इसे मान के बजाय केवल इसे पारित कर सकता है और स्थानीय चर के रूप में पैरामीटर का उपयोग कर सकता है।
मोड 3 का उपयोग करना मानक द्वारा वकालत करना प्रतीत होता है, जैसा कि इस तथ्य से गवाह है कि सभी प्रदान किए गए पुस्तकालय कार्य जो मोड 3 का उपयोग करके स्मार्ट पॉइंटर्स के स्वामित्व को स्थानांतरित करते हैं। बिंदु में एक विशेष रूप से ठोस मामला कंस्ट्रक्टर है std::shared_ptr<T>(auto_ptr<T>&& p)। उस कंस्ट्रक्टर का उपयोग std::tr1एक संशोधित लैवल्यू रेफरेंस ( auto_ptr<T>&कॉपी कंस्ट्रक्टर की तरह ) लेने के लिए किया जाता है, और इसलिए इसे एक auto_ptr<T>लैवल्यू के pरूप में कहा जा सकता है std::shared_ptr<T> q(p), जिसके बाद pइसे शून्य पर रीसेट कर दिया गया है। तर्क पास होने के मोड 2 से 3 में परिवर्तन के कारण, इस पुराने कोड को अब फिर से लिखना std::shared_ptr<T> q(std::move(p))होगा और फिर काम करना जारी रखना होगा। मैं समझता हूं कि समिति को यहां मोड 2 पसंद नहीं था, लेकिन उनके पास मोड 1 में बदलने का विकल्प था, परिभाषित करकेstd::shared_ptr<T>(auto_ptr<T> p)इसके बजाय, वे यह सुनिश्चित कर सकते थे कि पुराना कोड संशोधन के बिना काम करता है, क्योंकि (अद्वितीय-पॉइंटर्स के विपरीत) ऑटो-पॉइंटर्स को चुपचाप एक मूल्य के लिए निष्क्रिय किया जा सकता है (सूचक ऑब्जेक्ट खुद को प्रक्रिया में शून्य होने के लिए रीसेट किया जा रहा है)। जाहिरा तौर पर समिति ने मोड 1 से अधिक मोड 3 की वकालत को प्राथमिकता दी, ताकि वे पहले से ही उपयोग किए गए उपयोग के लिए भी मोड 1 का उपयोग करने के बजाय मौजूदा कोड को सक्रिय रूप से तोड़ सकें ।
जब मोड 3 से अधिक मोड 1 पसंद करना है
मोड 1 कई मामलों में पूरी तरह से प्रयोग करने योग्य है, और उन मामलों में मोड 3 से अधिक पसंद किया जा सकता है जहां स्वामित्व ग्रहण करना अन्यथा स्मार्ट पॉइंटर को स्थानीय चर में ले जाने का रूप लेता है जैसा कि reversedऊपर दिए गए उदाहरण में है। हालाँकि, मैं अधिक सामान्य मामले में मोड 3 को प्राथमिकता देने के दो कारण देख सकता हूं:
एक अस्थायी और निक्स को पुराने पॉइंटर बनाने की तुलना में एक संदर्भ पास करना थोड़ा अधिक कुशल है (नकदी को संभालना कुछ कठिन है); कुछ परिदृश्यों में सूचक को किसी अन्य फ़ंक्शन से अपरिवर्तित होने से पहले कई बार पारित किया जा सकता है, क्योंकि यह वास्तव में पायलट है। इस तरह के पारित होने के लिए आम तौर पर लेखन की आवश्यकता होगी std::move(जब तक कि मोड 2 का उपयोग नहीं किया जाता है), लेकिन ध्यान दें कि यह सिर्फ एक कास्ट है जो वास्तव में कुछ भी नहीं करता है (विशेष रूप से कोई डेरेफेरिंग नहीं), इसलिए इसमें शून्य लागत संलग्न है।
क्या यह बोधगम्य होना चाहिए कि फ़ंक्शन कॉल की शुरुआत और उस बिंदु के बीच कुछ भी फेंकता है, जहां यह (या कुछ निहित कॉल) वास्तव में एक अन्य डेटा संरचना में इंगित ऑब्जेक्ट को स्थानांतरित करता है (और यह अपवाद फ़ंक्शन के अंदर पहले से ही पकड़ा नहीं गया है) ), फिर मोड 1 का उपयोग करते समय, स्मार्ट पॉइंटर द्वारा संदर्भित ऑब्जेक्ट को catchखंड को अपवाद से पहले नष्ट कर दिया जाएगा (क्योंकि फ़ंक्शन पैरामीटर स्टैक अनइंडिंग के दौरान नष्ट हो गया था), लेकिन ऐसा नहीं है जब मोड 3 का उपयोग कर। बाद वाला देता है। कॉलर के पास ऐसे मामलों में ऑब्जेक्ट के डेटा को पुनर्प्राप्त करने का विकल्प होता है (अपवाद को पकड़कर)। ध्यान दें कि यहां मोड 1 स्मृति रिसाव का कारण नहीं बनता है , लेकिन इससे प्रोग्राम के लिए डेटा की अपरिवर्तनीय हानि हो सकती है, जो अवांछनीय भी हो सकती है।
एक स्मार्ट सूचक लौटना: हमेशा मूल्य से
एक स्मार्ट पॉइंटर को वापस करने के बारे में एक शब्द का निष्कर्ष निकालने के लिए , संभवतः कॉल करने वाले द्वारा उपयोग किए गए ऑब्जेक्ट के लिए इंगित किया गया है। यह वास्तव में कार्यों में गुजरने वाले बिंदुओं के साथ तुलनीय मामला नहीं है, लेकिन पूर्णता के लिए मैं इस बात पर जोर देना चाहूंगा कि ऐसे मामलों में हमेशा मूल्य से वापसी करें (और बयान में उपयोग न करें )। कोई भी एक पॉइंटर का संदर्भ प्राप्त नहीं करना चाहता है जो शायद सिर्फ निक्स किया गया है।std::movereturn