`Enable_sared_from_this` की उपयोगिता क्या है?


349

मैं enable_shared_from_thisBoost.Asio उदाहरणों को पढ़ते समय भर में भाग गया और दस्तावेज़ीकरण पढ़ने के बाद मैं अभी भी इसके लिए खो गया हूं कि इसे कैसे सही तरीके से उपयोग किया जाना चाहिए। क्या कोई मुझे एक उदाहरण और स्पष्टीकरण दे सकता है जब इस वर्ग का उपयोग करना समझ में आता है।

जवाबों:


362

यह आपको एक वैध shared_ptrउदाहरण प्राप्त करने में सक्षम बनाता है this, जब आपके पास सब कुछ होता है this। इसके बिना, आप एक होने का कोई रास्ता नहीं होता है shared_ptrकरने के लिए this, जब तक आप पहले से ही एक सदस्य के रूप में एक था। इस उदाहरण को enable_sared_from_this के लिए बढ़ावा देने के दस्तावेज़ से :

class Y: public enable_shared_from_this<Y>
{
public:

    shared_ptr<Y> f()
    {
        return shared_from_this();
    }
}

int main()
{
    shared_ptr<Y> p(new Y);
    shared_ptr<Y> q = p->f();
    assert(p == q);
    assert(!(p < q || q < p)); // p and q must share ownership
}

विधि f()एक वैध रिटर्न देती है shared_ptr, भले ही इसका कोई सदस्य उदाहरण न हो। ध्यान दें कि आप बस ऐसा नहीं कर सकते हैं:

class Y: public enable_shared_from_this<Y>
{
public:

    shared_ptr<Y> f()
    {
        return shared_ptr<Y>(this);
    }
}

साझा किए गए पॉइंटर कि यह वापस आ गया है, "उचित" एक से एक अलग संदर्भ गणना होगी, और उनमें से एक वस्तु को हटाए जाने पर एक झूलते हुए संदर्भ को खोने और धारण करना होगा।

enable_shared_from_thisC ++ 11 मानक का हिस्सा बन गया है। आप इसे वहां से और साथ ही बूस्ट से भी प्राप्त कर सकते हैं।


202
+1। मुख्य बिंदु यह है कि सिर्फ रिटर्निंग की "स्पष्ट" तकनीक साझा की गई है <y> (यह) टूटी हुई है, क्योंकि यह हवा अलग-अलग संदर्भ गणना के साथ कई अलग-अलग साझा_प्रटर ऑब्जेक्ट्स बनाने का काम करती है। इस कारण से आपको कभी भी एक ही कच्चे सूचक से एक से अधिक share_ptr नहीं बनाना चाहिए ।
j_random_hacker

3
यह ध्यान दिया जाना चाहिए कि सी ++ 11 और बाद में , अगर यह विरासत में मिला है तो एक कच्चे सूचक पर एक निर्माता का उपयोग करने के लिए पूरी तरह से वैध है । मुझे पता नहीं है कि बूस्ट के शब्दार्थ को इसका समर्थन करने के लिए अद्यतन किया गया था या नहीं। std::shared_ptr std::enable_shared_from_this
मैथ्यू

6
@MatthewHolder क्या आपके पास इसके लिए कोई उद्धरण है? Cppreference.com पर मैंने "किसी अन्य के std::shared_ptrलिए पहले से प्रबंधित वस्तु के लिए निर्माण std::shared_ptrकरना आंतरिक रूप से संग्रहीत कमजोर संदर्भ से परामर्श नहीं करेगा और इस प्रकार अपरिभाषित व्यवहार को बढ़ावा देगा।" ( en.cppreference.com/w/cpp/memory/enable_sared_from_this )
Thorbjørn Lindeijer

5
तुम सिर्फ क्यों नहीं कर सकते shared_ptr<Y> q = p?
डैन एम।

2
@ ThorbjørnLindeijer, आप सही हैं, यह C ++ 17 और बाद का है। कुछ कार्यान्वयन ने रिलीज़ होने से पहले C ++ 16 शब्दार्थ का पालन किया। C ++ 11 से C ++ 14 के लिए उचित हैंडलिंग का उपयोग करना चाहिए std::make_shared<T>
मैथ्यू

198

कमजोर बिंदुओं पर डॉ डोब्स लेख से, मुझे लगता है कि यह उदाहरण समझना आसान है (स्रोत: http://drdobbs.com/cpp/184402026 ):

... इस तरह कोड सही ढंग से काम नहीं करेगा:

int *ip = new int;
shared_ptr<int> sp1(ip);
shared_ptr<int> sp2(ip);

दोनों में से कोई भी shared_ptrवस्तु दूसरे के बारे में नहीं जानती है, इसलिए दोनों नष्ट होने पर संसाधन को छोड़ने की कोशिश करेंगे। यह आमतौर पर समस्याओं की ओर जाता है।

इसी प्रकार, यदि किसी सदस्य को किसी shared_ptrऑब्जेक्ट की आवश्यकता होती है जो उस वस्तु का मालिक है जिसे उस पर कॉल किया जा रहा है, तो यह केवल मक्खी पर ऑब्जेक्ट नहीं बना सकता है:

struct S
{
  shared_ptr<S> dangerous()
  {
     return shared_ptr<S>(this);   // don't do this!
  }
};

int main()
{
   shared_ptr<S> sp1(new S);
   shared_ptr<S> sp2 = sp1->dangerous();
   return 0;
}

इस कोड में पहले के उदाहरण के समान समस्या है, हालांकि अधिक सूक्ष्म रूप में। जब इसका निर्माण किया जाता है, तो shared_ptr ऑब्जेक्ट sp1नए आवंटित संसाधन का मालिक होता है। सदस्य फ़ंक्शन के अंदर का कोड S::dangerousउस shared_ptrऑब्जेक्ट के बारे में नहीं जानता है , इसलिए वह जिस shared_ptrऑब्जेक्ट से रिटर्न करता है वह अलग है sp1। मदद shared_ptrकरने के लिए नई वस्तु की नकल करना sp2; जब sp2यह कार्यक्षेत्र से बाहर हो जाता है, तो यह संसाधन को छोड़ देगा, और जब sp1यह कार्यक्षेत्र से बाहर चला जाएगा, तो यह संसाधन को फिर से जारी करेगा।

इस समस्या से बचने का तरीका वर्ग टेम्पलेट का उपयोग करना है enable_shared_from_this। टेम्पलेट एक टेम्पलेट प्रकार तर्क लेता है, जो उस वर्ग का नाम है जो प्रबंधित संसाधन को परिभाषित करता है। उस वर्ग को, खासतौर पर, खाके से सार्वजनिक रूप से प्राप्त किया जाना चाहिए; इस तरह:

struct S : enable_shared_from_this<S>
{
  shared_ptr<S> not_dangerous()
  {
    return shared_from_this();
  }
};

int main()
{
   shared_ptr<S> sp1(new S);
   shared_ptr<S> sp2 = sp1->not_dangerous();
   return 0;
}

जब आप ऐसा करते हैं, तो ध्यान रखें कि जिस ऑब्जेक्ट पर आप कॉल करते हैं वह ऑब्जेक्ट के shared_from_thisस्वामित्व में होना चाहिए shared_ptr। यह काम नहीं करेगा:

int main()
{
   S *p = new S;
   shared_ptr<S> sp2 = p->not_dangerous();     // don't do this
}

15
धन्यवाद, यह वर्तमान में स्वीकृत उत्तर की तुलना में बेहतर हल की जा रही समस्या को दर्शाता है।
goertzenator

2
+1: अच्छा जवाब। एक तरफ के रूप में, इसके बजाय shared_ptr<S> sp1(new S);इसका उपयोग करना पसंद किया जा सकता है shared_ptr<S> sp1 = make_shared<S>();, उदाहरण के लिए देखें stackoverflow.com/questions/18301511/…
अरुण

4
मुझे पूरा यकीन है कि अंतिम पंक्ति को पढ़ना चाहिए shared_ptr<S> sp2 = p->not_dangerous();क्योंकि यहाँ नुकसान यह है कि आपको पहली बार कॉल करने से पहले सामान्य तरीके से एक साझा करना चाहिए shared_from_this()! यह गलत पाने के लिए वास्तव में आसान है! C ++ 17 से पहले यह यूबी कॉल करना है shared_from_this()इससे पहले कि वास्तव में एक साझा किया गया है सामान्य तरीके से बनाया गया है: auto sptr = std::make_shared<S>();या shared_ptr<S> sptr(new S());। C ++ 17 से शुक्र है कि ऐसा करने से फेंक दिया जाएगा।
AnorZaken

2
BAD उदाहरण: S* s = new S(); shared_ptr<S> ptr = s->not_dangerous();<- इसे share_from_this को केवल पहले से साझा की गई वस्तु पर कॉल करने की अनुमति है, अर्थात std द्वारा साझा की गई वस्तु पर: share_ptr <T>। अन्यथा व्यवहार अपरिभाषित है (C ++ 17 तक) std :: bad_weak_ptr फेंक दिया जाता है (डिफ़ॉल्ट-निर्मित कमजोर_तीस से share_ptr निर्माता द्वारा) (C ++ 17 के बाद से)। । तो वास्तविकता यह है कि इसे बुलाया जाना चाहिए always_dangerous(), क्योंकि आपको ज्ञान की आवश्यकता है कि क्या यह पहले से ही साझा किया गया है या नहीं।
AnorZaken

2
@AnorZaken अच्छा बिंदु। यदि आप उस फिक्स को बनाने के लिए एक संपादन अनुरोध प्रस्तुत कर चुके होते तो यह उपयोगी होता। मैंने अभी-अभी किया है। दूसरी उपयोगी चीज पोस्टर के लिए व्यक्तिपरक, संदर्भ-संवेदनशील विधि नामों का चयन नहीं करना होगा!
अंडरस्कोर_ड

30

यहाँ एक नट और बोल्ट के दृष्टिकोण से मेरा स्पष्टीकरण है (शीर्ष उत्तर ने मेरे साथ 'क्लिक' नहीं किया)। * ध्यान दें कि यह श्रुति_पट्र और enable_sared_from_this के लिए स्रोत की जाँच करने का परिणाम है जो विज़ुअल स्टूडियो 2012 के साथ आता है। शायद अन्य कंपाइलर enable_sared_from_this को अलग तरह से लागू करते हैं ... *

enable_shared_from_this<T>एक निजी weak_ptr<T>उदाहरण जोड़ता है Tजो उदाहरण के लिए ' एक सही संदर्भ गणना ' रखता है T

इसलिए, जब आप पहली बार shared_ptr<T>एक नया T * बनाते हैं , तो वह T * का आंतरिक कमज़ोर_प्रकार 1. के एक रिफकाउंट से आरंभ हो जाता है। नया shared_ptrमूल रूप से इस पर वापस आ जाता है weak_ptr

Tतब, इसके तरीकों में, shared_from_thisएक ही आंतरिक रूप से संग्रहीत संदर्भ गणना परshared_ptr<T> उस बैक का उदाहरण प्राप्त करने के लिए कॉल कर सकते हैं । इस तरह, आपके पास हमेशा एक जगह होती है, जहाँ पर T*कई-कई shared_ptrउदाहरणों के बजाय रेफ-काउंट को संग्रहित किया जाता है, जो एक -दूसरे के बारे में नहीं जानते हैं, और प्रत्येक सोचते हैं कि वे shared_ptrरेफ-काउंटिंग के प्रभारी हैं Tऔर इसे तब हटाते हैं जब उनका रिफाइन होता है -काउंट शून्य तक पहुंचता है।


1
यह सही है, और वास्तव में महत्वपूर्ण हिस्सा है, So, when you first create...क्योंकि यह एक आवश्यकता है (जैसा कि आप कहते हैं कि weak_ptr को तब तक आरंभीकृत नहीं किया जाता है, जब तक आप ऑब्जेक्ट्स पॉइंटर को एक साझा_ crr!) में पास नहीं करते हैं और यह वह जगह है जहाँ चीजें आपके लिए बहुत गलत हो सकती हैं! सावधान नहीं। यदि आप कॉल करने से पहले कोई साझा नहीं shared_from_thisकरते हैं तो आप यूबी प्राप्त करते हैं - इसी तरह यदि आप एक से अधिक साझा किए गए हैं तो आप यूबी भी प्राप्त कर सकते हैं। आप किसी भी तरह सुनिश्चित करें कि आप एक shared_ptr बनाने करना है वास्तव में एक बार।
AnorZaken

2
दूसरे शब्दों में enable_shared_from_this, बिंदु से शुरू करने के लिए सक्षम होने के बाद से शुरू करने के लिए पूरी तरह से भंगुर shared_ptr<T>है T*, लेकिन वास्तव में जब आपको एक संकेतक मिलता है, T* tतो यह आम तौर पर इसके बारे में कुछ भी मानने के लिए सुरक्षित नहीं है कि यह पहले से ही साझा किया जा रहा है या नहीं, और गलत अनुमान लगाना यूबी है।
AnorZaken

" आंतरिक weak_ptr को 1 के रिफंड के साथ आरंभीकृत किया जाता है " कमजोर ptr से T गैर-स्वामित्व वाले स्मार्ट ptr होते हैं। T. एक कमजोर ptr एक मालिक के लिए पर्याप्त जानकारी देने के लिए एक चालाक स्मार्ट रेफरी है जो ptr कि अन्य मालिक ptr की "कॉपी" है। एक कमजोर ptr की कोई गिनती नहीं है। यह सभी स्वयं के रेफरी की तरह एक रेफ़ काउंट तक पहुंच रखता है।
उत्सुक

3

ध्यान दें कि एक बढ़ावा :: intrusive_ptr इस समस्या से ग्रस्त नहीं है। यह अक्सर इस मुद्दे के आसपास आने का एक अधिक सुविधाजनक तरीका है।


हां, लेकिन enable_shared_from_thisआपको एक एपीआई के साथ काम करने की अनुमति देता है जो विशेष रूप से स्वीकार करता है shared_ptr<>। मेरी राय में, इस तरह की एपीआई आमतौर पर डूइंग इट गलत है (जैसा कि स्टैक में मेमोरी को कुछ अधिक होने देना बेहतर है), लेकिन अगर आप इस तरह के एपीआई के साथ काम करने के लिए मजबूर हैं, तो यह एक अच्छा विकल्प है।
cdunn2001

2
जितना हो सके मानक के भीतर रहने के लिए बेहतर है।
सर्गेई

3

यह c ++ 11 और बाद में बिल्कुल वैसा ही है: यह thisएक साझा पॉइंटर के रूप में वापस जाने की क्षमता को सक्षम करने के लिए है क्योंकि thisआप एक कच्चा पॉइंटर देते हैं।

दूसरे शब्दों में, यह आपको इस तरह कोड को चालू करने की अनुमति देता है

class Node {
public:
    Node* getParent const() {
        if (m_parent) {
            return m_parent;
        } else {
            return this;
        }
    }

private:

    Node * m_parent = nullptr;
};           

इस मामले में:

class Node : std::enable_shared_from_this<Node> {
public:
    std::shared_ptr<Node> getParent const() {
        std::shared_ptr<Node> parent = m_parent.lock();
        if (parent) {
            return parent;
        } else {
            return shared_from_this();
        }
    }

private:

    std::weak_ptr<Node> m_parent;
};           

यह तभी काम करेगा जब इन वस्तुओं को हमेशा एक द्वारा प्रबंधित किया जाता है shared_ptr। आप यह सुनिश्चित करने के लिए इंटरफ़ेस बदलना चाह सकते हैं कि यह मामला है।
जिज्ञासु

1
आप बिल्कुल सही हैं @curiousguy यह बिना कहे चला जाता है। मुझे अपने सार्वजनिक APIs को परिभाषित करते समय पठनीयता में सुधार करने के लिए अपने सभी साझा करने के लिए typedef-ing पसंद है। उदाहरण के लिए, इसके बजाय std::shared_ptr<Node> getParent const(), मैं सामान्य रूप से NodePtr getParent const()इसके बजाय इसे उजागर करूंगा । यदि आपको आंतरिक कच्चे पॉइंटर (सर्वोत्तम उदाहरण: सी लाइब्रेरी से निपटने) तक पहुंच की आवश्यकता है, तो उसके std::shared_ptr<T>::getलिए है, जिसका मैं उल्लेख करना पसंद करता हूं क्योंकि मैंने इस कच्चे पॉइंटर एक्सेसर का उपयोग कई बार गलत कारण से किया है।
मचिअसन

-3

दूसरा तरीका यह है कि किसी weak_ptr<Y> m_stubसदस्य को इसमें जोड़ें class Y। फिर लिखें:

shared_ptr<Y> Y::f()
{
    return m_stub.lock();
}

उपयोगी तब होता है जब आप उस वर्ग को नहीं बदल सकते जो आप से प्राप्त कर रहे हैं (उदाहरण के लिए अन्य लोगों के पुस्तकालय का विस्तार)। सदस्य को इनिशियलाइज़ करना न भूलें, उदाहरण के लिए m_stub = shared_ptr<Y>(this), एक कंस्ट्रक्टर के दौरान भी यह मान्य है।

यह ठीक है अगर वंशानुक्रम पदानुक्रम में इस तरह के अधिक स्टब्स हैं, तो यह ऑब्जेक्ट के विनाश को रोक नहीं पाएगा।

संपादित करें: जैसा कि उपयोगकर्ता nobar द्वारा सही ढंग से बताया गया है, असाइनमेंट समाप्त होने पर कोड Y ऑब्जेक्ट को नष्ट कर देगा और अस्थायी चर नष्ट हो जाएंगे। इसलिए मेरा उत्तर गलत है।


4
यदि आपका इरादा यहां ऐसा उत्पादन करना है shared_ptr<>जो अपनी पॉइंटर को नहीं हटाता है, तो यह ओवरकिल है। आप बस यह कह सकते हैं कि एक क्रियात्मक वस्तु return shared_ptr<Y>(this, no_op_deleter);कहां no_op_deleterहै Y*और कुछ भी नहीं ले रही है।
जॉन ज़नकॉक

2
यह संभव नहीं लगता है कि यह एक कार्यशील समाधान है। m_stub = shared_ptr<Y>(this)निर्माण करेगा और इससे एक अस्थायी शेयर्ड_एप्ट्र को तुरंत नष्ट कर देगा। जब यह कथन समाप्त thisहो जाएगा , तो हटा दिया जाएगा और बाद के सभी संदर्भ झूलने लगेंगे।
नोबार

2
लेखक इस जवाब को गलत मानता है इसलिए वह शायद इसे हटा सकता है। लेकिन उन्होंने 4.5 साल में आखिरी बार लॉग इन किया था इसलिए ऐसा करने की संभावना नहीं है - क्या उच्च शक्तियों वाला कोई व्यक्ति इस लाल हेरिंग को हटा सकता है?
टॉम गुडफेलो

यदि आप के कार्यान्वयन को देखते हैं enable_shared_from_this, तो यह weak_ptrअपने आप में (ctor द्वारा आबाद) रहता है, shared_ptrजब आप कॉल करते हैं shared_from_this। दूसरे शब्दों में, आप enable_shared_from_thisपहले से ही जो प्रदान करते हैं, उसकी नकल कर रहे हैं ।
मोचीसन 16
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.