साझा बिंदुओं को तर्क के रूप में पारित करना


88

अगर मैं एक साझा सूचक में लिपटी हुई वस्तु घोषित करता हूं:

std::shared_ptr<myClass> myClassObject(new myClass());

तब मैं इसे एक विधि के तर्क के रूप में पारित करना चाहता था:

DoSomething(myClassObject);

//the called method
void DoSomething(std::shared_ptr<myClass> arg1)
{
   arg1->someField = 4;
}

क्या उपरोक्त केवल साझा_ संदर्भ संदर्भ गणना और सब कुछ शांत है? या यह एक लटकते हुए सूचक को छोड़ देता है?

क्या आप अभी भी ऐसा करने वाले हैं? "

DoSomething(myClassObject.Get());

void DoSomething(std::shared_ptr<myClass>* arg1)
{
   (*arg1)->someField = 4;
}

मुझे लगता है कि दूसरा तरीका अधिक कुशल हो सकता है, क्योंकि इसमें केवल 1 पते (जैसा कि पूरे स्मार्ट पॉइंटर के विपरीत) को कॉपी करना है, लेकिन 1 तरीका अधिक पठनीय लगता है और मैं प्रदर्शन सीमाओं को आगे बढ़ाने का अनुमान नहीं लगाता। मैं सिर्फ यह सुनिश्चित करना चाहता हूं कि इसके बारे में कुछ खतरनाक नहीं है।

धन्यवाद।


14
const std::shared_ptr<myClass>& arg1
कैप्टन ऑब्लियस

3
दूसरा रास्ता टूट गया है, पहला मुहावरेदार है अगर आपको स्वामित्व साझा करने के लिए वास्तव में आपके कार्य की आवश्यकता है। लेकिन, क्या DoSomethingवास्तव में स्वामित्व साझा करने की आवश्यकता है? ऐसा लग रहा है कि इसे सिर्फ एक संदर्भ लेना चाहिए ...
ildjarn

8
@SteveH: यह नहीं है, लेकिन अगर आपके फ़ंक्शन को अपने कॉलर्स पर विशेष ऑब्जेक्ट स्वामित्व शब्दार्थ को मजबूर करना चाहिए, अगर यह वास्तव में उनकी आवश्यकता नहीं है? आपका कार्य कुछ भी नहीं करता है जो बेहतर नहीं होगा void DoSomething(myClass& arg1)
ildjarn

2
@SteveH: स्मार्ट पॉइंटर्स का पूरा उद्देश्य साधारण वस्तु स्वामित्व के मुद्दों से निपटना है - यदि आपके पास वे नहीं हैं, तो आपको पहले स्थान पर स्मार्ट पॉइंटर्स का उपयोग नहीं करना चाहिए। ; -] shared_ptr<>विशेष रूप से , वास्तव में स्वामित्व साझा करने के लिए आपको मूल्य से गुजरना होगा
iljarn

4
असंबंधित: सामान्य रूप से, std::make_sharedन केवल अधिक कुशल है, बल्कि निर्माणकर्ता की तुलना में अधिक सुरक्षित है std::shared_ptr
आर। मार्टिनो फर्नांडिस

जवाबों:


171

मैं एक फ़ंक्शन के लिए एक साझा पॉइंटर पास करना चाहता हूं। क्या उसके लिए आपके द्वारा मेरी मदद की जाएगी?

ज़रूर, मैं आपकी मदद कर सकता हूं। मुझे लगता है कि आपको C ++ में स्वामित्व शब्दार्थ की कुछ समझ है। क्या यह सच है?

हाँ, मैं इस विषय के साथ काफी सहज हूं।

अच्छा।

ठीक है, मैं shared_ptrतर्क लेने के लिए केवल दो कारणों के बारे में सोच सकता हूं :

  1. फ़ंक्शन ऑब्जेक्ट का स्वामित्व साझा करना चाहता है;
  2. फ़ंक्शन कुछ ऑपरेशन करता है जो विशेष रूप से shared_ptrs पर काम करता है ।

आप किस में रुचि रखते हैं?

मैं एक सामान्य उत्तर की तलाश में हूं, इसलिए मुझे वास्तव में दोनों में दिलचस्पी है। मैं इस मामले में उत्सुक हूं कि आपके मामले में # 2 का क्या मतलब है।

ऐसे कार्यों के उदाहरणों में शामिल हैं std::static_pointer_cast, कस्टम तुलनित्र, या विधेय। उदाहरण के लिए, यदि आपको एक वेक्टर से सभी अनूठे शेयर्ड_एप्ट्र को खोजने की आवश्यकता है, तो आपको इस तरह के एक विधेय की आवश्यकता है।

आह, जब फ़ंक्शन को वास्तव में खुद स्मार्ट पॉइंटर को हेरफेर करने की आवश्यकता होती है।

बिल्कुल सही।

उस मामले में, मुझे लगता है कि हमें संदर्भ से गुजरना चाहिए।

हाँ। और अगर यह पॉइंटर नहीं बदलता है, तो आप कॉन्स्ट रेफरेंस से गुजरना चाहते हैं। जब से आपको स्वामित्व साझा करने की आवश्यकता नहीं है, तब तक कॉपी करने की कोई आवश्यकता नहीं है। वह दूसरा परिदृश्य है।

ठीक मिल गया। दूसरे परिदृश्य के बारे में बात करते हैं।

वह जहां आप स्वामित्व साझा करते हैं? ठीक। आप स्वामित्व कैसे साझा करते हैं shared_ptr?

इसकी नकल करके।

फिर फ़ंक्शन को एक की प्रतिलिपि बनाने की आवश्यकता होगी shared_ptr, सही?

जाहिर है। तो मैं इसे एक कॉन्स्टेबल और एक स्थानीय वैरिएबल को कॉपी करने के संदर्भ में पास करता हूं?

नहीं, वह निराशावाद है। यदि इसे संदर्भ द्वारा पारित किया जाता है, तो फ़ंक्शन को कॉपी मैन्युअल रूप से बनाने के अलावा कोई विकल्प नहीं होगा। यदि यह मान से पारित हो जाता है तो संकलक एक प्रतिलिपि और एक चाल के बीच सबसे अच्छा विकल्प चुन लेगा और स्वचालित रूप से प्रदर्शन करेगा। तो, मूल्य से गुजारें।

अच्छी बात। मुझे याद रखना चाहिए कि " वांट स्पीड? पास बाय वैल्यू। " लेख अधिक बार।

प्रतीक्षा करें, क्या होगा यदि फ़ंक्शन shared_ptrएक सदस्य चर में संग्रहीत करता है, उदाहरण के लिए? क्या यह एक निरर्थक प्रति नहीं होगी?

फ़ंक्शन केवल shared_ptrतर्क को उसके संग्रहण में स्थानांतरित कर सकता है। एक चलती shared_ptrसस्ता है, क्योंकि यह किसी भी संदर्भ में गिना जाता है नहीं बदलता है।

आह, अच्छा विचार है।

लेकिन मैं तीसरे परिदृश्य के बारे में सोच रहा हूं: क्या होगा यदि आप इसमें हेरफेर नहीं करना चाहते हैं shared_ptr, न ही स्वामित्व साझा करना चाहते हैं?

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

और क्या मुझे संदर्भ द्वारा या मूल्य के आधार पर सूचक को लेना चाहिए?

सामान्य नियम लागू होते हैं। स्मार्ट संकेत कुछ भी नहीं बदलते हैं।

यदि मैं प्रतिलिपि बनाने जा रहा हूं, तो मान से पास करें यदि मैं प्रतिलिपि से बचना चाहता हूं तो संदर्भ द्वारा पास करें।

सही।

हम्म। मुझे लगता है कि आप अभी तक एक और परिदृश्य भूल गए। क्या होगा यदि मैं स्वामित्व साझा करना चाहता हूं, लेकिन केवल एक निश्चित शर्त पर निर्भर करता है?

आह, एक दिलचस्प बढ़त का मामला। मुझे उम्मीद नहीं है कि अक्सर ऐसा होगा। लेकिन जब ऐसा होता है तो आप या तो मान से गुजर सकते हैं और यदि आपको इसकी आवश्यकता नहीं है, तो कॉपी को अनदेखा कर सकते हैं, या संदर्भ द्वारा पास कर सकते हैं और यदि आपको इसकी आवश्यकता है तो प्रतिलिपि बना सकते हैं।

मैं पहले विकल्प में एक निरर्थक कॉपी को जोखिम में डालता हूं, और दूसरे में एक संभावित कदम खो देता हूं। क्या मैं केक नहीं खा सकता और यह भी है?

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

मुझे लगता है कि सभी संभावित परिदृश्यों को शामिल किया गया है। आपका बहुत बहुत धन्यवाद।


2
@ जॉन: किस लिए? यह सब मुझे A करना चाहिए या मुझे B करना चाहिए । मुझे लगता है कि हम सभी जानते हैं कि वस्तुओं को मूल्य / संदर्भ द्वारा कैसे पारित किया जाए, नहीं?
sbi

9
क्योंकि गद्य की तरह "क्या इसका मतलब है कि मैं इसे कॉपी बनाने के लिए एक संदर्भ द्वारा पास कर दूंगा? नहीं, यह एक निराशा है" एक शुरुआत के लिए भ्रमित कर रहा है। Const के संदर्भ में पास होने से कॉपी नहीं बनती है।
जॉन

1
@Martinho मैं नियम से असहमत हूं "यदि आप पॉइन्ट्री में हेरफेर करना चाहते हैं, तो पॉइन्टी ले लें"। जैसा कि इस उत्तर में कहा गया है कि किसी वस्तु का अस्थायी स्वामित्व नहीं लेना खतरनाक हो सकता है। मेरी राय में फ़ंक्शन कॉल की अवधि के लिए ऑब्जेक्ट वैधता की गारंटी के लिए डिफ़ॉल्ट को अस्थायी स्वामित्व के लिए एक प्रति पारित करना चाहिए।
राडमान

@radman उस नियम को लागू करने के लिए आपके द्वारा लिंक किए गए उत्तर में कोई परिदृश्य नहीं है, इसलिए यह पूरी तरह अप्रासंगिक है। कृपया मुझे एक SSCCE लिखें जहाँ आप संदर्भ से गुजरते हैं shared_ptr<T> ptr;(यानी void f(T&), साथ बुलाया f(*ptr)) और ऑब्जेक्ट कॉल की रूपरेखा नहीं देता है। वैकल्पिक रूप से वह लिखें जहां आप मूल्य void f(T)और परेशानी के हमलों से गुजरते हैं ।
आर। मार्टिनो फर्नांडिस

@Martinho ने सरल उदाहरण के लिए मेरे उदाहरण कोड की जाँच की जिसमें सही उदाहरण था। उदाहरण void f(T&)थ्रेड्स के लिए है और इसमें शामिल है। मुझे लगता है कि मेरा मुद्दा यह है कि आपके उत्तर का लहजा साझा करने के स्वामित्व को हतोत्साहित करता है, जो कि उनके उपयोग का सबसे सुरक्षित, सबसे सहज और सबसे जटिल तरीका है। मैं कभी भी एक शुरुआत के लिए एक साझा करने की सलाह देने के खिलाफ बेहद साझा करने के खिलाफ होऊंगा, एक साझा_प्रति के स्वामित्व वाले डेटा के लिए <> दुरुपयोग की संभावनाएं महान हैं और लाभ कम से कम है।
राडमान

22

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

void DoSomething(myClass * p);

DoSomething(myClass_shared_ptr.get());
DoSomething(myClass_unique_ptr.get());

फ़ंक्शन पैरामीटर के रूप में एक कच्चा पॉइंटर आपको कॉलिंग कोड में स्मार्ट पॉइंटर्स का उपयोग करने से नहीं रोकता है, जहां यह वास्तव में मायने रखता है।


8
यदि आप एक पॉइंटर का उपयोग कर रहे हैं, तो केवल संदर्भ का उपयोग क्यों न करें? DoSomething(*a_smart_ptr)
Xio

5
@ Xeo, आप सही हैं - यह और भी बेहतर होगा। कभी-कभी आपको NULL पॉइंटर की संभावना के लिए अनुमति देने की आवश्यकता होती है।
मार्क रैनसम

1
तुलना: यदि आप उत्पादन पैरामीटर के रूप में कच्चे संकेत का उपयोग कर के एक सम्मेलन है, तो यह जैसे कोड है कि एक चर बदल सकता है, फोन करने में पहचानना आसान है fun(&x)करने के लिए fun(x)। बाद के उदाहरण में xया तो मूल्य से या एक कॉन्स्टेंट रेफरी के रूप में पारित किया जा सकता है। जैसा कि ऊपर कहा गया है कि nullptrयदि आप आउटपुट में रुचि नहीं रखते हैं तो भी आपको उत्तीर्ण होने की अनुमति दी जा सकती है ...
एंड्रियास मैग्नेसन

2
@AndreasMagnusson: पॉइंटर-अस-आउटपुट-पैरामीटर कन्वेंशन जब किसी अन्य स्रोत से पारित किए गए पॉइंटर्स से निपटते समय सुरक्षा की झूठी भावना दे सकता है। क्योंकि उस स्थिति में कॉल मजेदार है (x) और * x संशोधित है और स्रोत के पास आपको चेतावनी देने के लिए कोई और x नहीं है।
ज़ैन लिंक्स

क्या होगा यदि फ़ंक्शन कॉल कॉलर के दायरे को रेखांकित करता है? ऐसी संभावना है कि साझा ptr के विध्वंसक को बुलाया गया है, और अब यह फ़ंक्शन हटाए गए मेमोरी तक पहुंचने का प्रयास करेगा, जिसके परिणामस्वरूप UB
श्रीधर त्यागराजन

4

हां, एक साझा_प्रति <> के बारे में संपूर्ण विचार यह है कि कई उदाहरण एक ही कच्चे पॉइंटर को पकड़ सकते हैं और अंतर्निहित मेमोरी को केवल तभी मुक्त किया जाएगा जब शेयर्ड_प्ट्र <का अंतिम उदाहरण नष्ट हो जाए।

मैं एक सूचक को एक साझा_प्रा </ b> से बचने से बचता हूं क्योंकि यह उद्देश्य को हरा देता है क्योंकि आप अब कच्चे_पॉइंटर्स के साथ फिर से काम कर रहे हैं।


2

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


3
कोई भी सूचक द्वारा पास करने की वकालत नहीं कर रहा है, यदि आपका मतलब कच्चे सूचक से है। यदि कोई स्वामित्व विवाद नहीं है, तो संदर्भ से गुजरना निश्चित रूप से मुहावरेदार है। मुद्दा यह है shared_ptr<>कि विशेष रूप से, कि वास्तव में स्वामित्व साझा करने के लिए आपको एक प्रतिलिपि बनाना चाहिए ; यदि आपके पास एक संदर्भ या सूचक है shared_ptr<>तो आप कुछ भी साझा नहीं कर रहे हैं, और सामान्य संदर्भ या सूचक के समान जीवनकाल के मुद्दों के अधीन हैं।
इलडार्जन

2
@ गिल्डरन: एक निरंतर संदर्भ पास करना इस मामले में ठीक है। shared_ptrकॉल करने वाले के मूल को फंक्शन कॉल की रूपरेखा बनाने की गारंटी दी जाती है, इसलिए फ़ंक्शन का उपयोग करने में सुरक्षित है shared_ptr<> const &। यदि इसे बाद के समय के लिए सूचक को संग्रहीत करने की आवश्यकता है, तो इसे कॉपी करना होगा (इस बिंदु पर एक प्रतिलिपि बनाई जानी चाहिए, एक संदर्भ रखने के बजाय), लेकिन जब तक आपको ज़रूरत हो, तब तक प्रतिलिपि की लागत को लागू करने की आवश्यकता नहीं है यह। मैं इस मामले में एक निरंतर संदर्भ पारित करने के लिए जाऊंगा ....
डेविड रोड्रिज -

3
@ डेविड: " इस मामले में एक निरंतर संदर्भ पास करना ठीक है। " किसी भी समझदार एपीआई में नहीं - एपीआई एक स्मार्ट पॉइंटर प्रकार को क्यों अनिवार्य करेगा जो केवल सामान्य कॉन्स्ट रेफरेंस लेने के बजाय इसका फायदा नहीं उठा रहा है? यह लगभग कब्ज की कमी के रूप में बुरा है। यदि आपकी बात यह है कि तकनीकी रूप से यह कुछ भी नहीं कर रहा है, तो मैं निश्चित रूप से सहमत हूं, लेकिन मुझे नहीं लगता कि यह करना सही है।
इलडार्जन

2
... यदि दूसरी तरफ, फ़ंक्शन को ऑब्जेक्ट के जीवनकाल का विस्तार करने की आवश्यकता नहीं है, तो आप बस shared_ptrइंटरफ़ेस से निकाल सकते हैं और constइंगित ऑब्जेक्ट के लिए एक सादा ( ) संदर्भ पास कर सकते हैं ।
डेविड रॉड्रिग्ज -

3
@ डेविड: यदि आपका एपीआई उपभोक्ता shared_ptr<>शुरू करने के लिए उपयोग नहीं कर रहा है तो क्या होगा ? अब आपका एपीआई वास्तव में सिर्फ गर्दन में दर्द है, क्योंकि यह फोन करने वाले को केवल इसका उपयोग करने के लिए अपने ऑब्जेक्ट जीवनकाल शब्दार्थ को बदलने के लिए मजबूर करता है। मुझे इस बात की वकालत करने लायक कुछ भी नहीं दिखाई दे रहा है कि "यह तकनीकी रूप से किसी चीज को नुकसान नहीं पहुंचाता है।" मुझे 'यदि आपको साझा स्वामित्व की आवश्यकता नहीं है, तो उपयोग न करें shared_ptr<>' के बारे में कुछ भी विवादास्पद नहीं है ।
इलडार्जन

0

आपके कार्य में DoSomething आप वर्ग के एक डेटा सदस्य को बदल रहे हैं myClass ताकि आप जो संशोधित कर रहे हैं वह प्रबंधित (रॉ पॉइंटर) ऑब्जेक्ट है (साझा नहीं किया गया) ऑब्जेक्ट। इस फ़ंक्शन के वापसी बिंदु पर जिसका अर्थ है कि प्रबंधित कच्चे पॉइंटर में सभी साझा किए गए पॉइंटर्स अपने डेटा सदस्य को देखेंगे: myClass::someFieldएक अलग मान में परिवर्तित हो जाएगा।

इस मामले में, आप किसी फ़ंक्शन को इस गारंटी के साथ पास कर रहे हैं कि आप इसे संशोधित नहीं कर रहे हैं (साझा किए गए के बारे में बात कर रहे हैं / स्वामित्व वाली वस्तु नहीं)।

यह व्यक्त करने के लिए मुहावरा है: एक कास्ट रेफरी, जैसे

void DoSomething(const std::shared_ptr<myClass>& arg)

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

CAVEAT: जिसका अर्थ है, अगर किसी तरह से कोई shared_ptr::resetआपके फ़ंक्शन को कॉल करने से पहले कॉल करता है, और उस पल में यह last_ptr है, जो raw_ptr का मालिक है, तो आपकी ऑब्जेक्ट नष्ट हो जाएगी, और आपका फ़ंक्शन ऑब्जेक्ट को नष्ट करने के लिए एक झूलने वाला सूचक को हेरफेर करेगा। बहूत खतरनाक!!!

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