C ++ में Make_saring और normal shared_ptr में अंतर


276
std::shared_ptr<Object> p1 = std::make_shared<Object>("foo");
std::shared_ptr<Object> p2(new Object("foo"));

इस पर कई google और stackoverflow पोस्ट हैं, लेकिन मैं यह समझने में सक्षम नहीं हूं कि make_sharedसीधे उपयोग करने की तुलना में अधिक कुशल क्यों है shared_ptr

क्या कोई मुझे बनाई गई वस्तुओं के चरणबद्ध अनुक्रम और दोनों के द्वारा किए गए कार्यों की व्याख्या कर सकता है ताकि मैं समझ सकूँ make_sharedकि कार्यकुशलता कितनी है। मैंने संदर्भ के लिए ऊपर एक उदाहरण दिया है।


4
यह अधिक कुशल नहीं है। इसका उपयोग करने का कारण अपवाद सुरक्षा के लिए है।
युशु

कुछ लेख कहते हैं, यह कुछ निर्माण ओवरहेड से बचा जाता है, क्या आप इस पर अधिक व्याख्या कर सकते हैं?
अनूप बुचके

16
@ युयुशी: अपवाद सुरक्षा इसका उपयोग करने का एक अच्छा कारण है, लेकिन यह अधिक कुशल भी है।
माइक सेमुर

3
32:15 वह जगह है जहां वह वीडियो में शुरू होता है जो मैंने ऊपर से जोड़ा था, अगर वह मदद करता है।
क्रिस

4
मामूली कोड शैली का लाभ: make_sharedआप लिख सकते हैं auto p1(std::make_shared<A>())और p1 का सही प्रकार होगा।
इवान वर्गीज ११'१४

जवाबों:


333

अंतर यह है कि std::make_sharedएक ढेर-आवंटन का प्रदर्शन करता है, जबकि std::shared_ptrनिर्माणकर्ता को बुलाकर दो कार्य करता है।

ढेर-आवंटन कहाँ होते हैं?

std::shared_ptr दो संस्थाओं का प्रबंधन करता है:

  • नियंत्रण ब्लॉक (मेटा डेटा संग्रहीत करता है, जैसे कि रिफ-काउंट्स, टाइप-एरीड डीलेटर, आदि)
  • ऑब्जेक्ट को प्रबंधित किया जा रहा है

std::make_sharedनियंत्रण ब्लॉक और डेटा दोनों के लिए आवश्यक स्थान के लिए एकल हीप-आवंटन लेखांकन करता है। अन्य मामले में, new Obj("foo")प्रबंधित डेटा के लिए एक ढेर-आवंटन को आमंत्रित std::shared_ptrकरता है और निर्माणकर्ता नियंत्रण ब्लॉक के लिए एक और प्रदर्शन करता है।

अधिक जानकारी के लिए, cppreference पर कार्यान्वयन नोट्स देखें

अद्यतन I: अपवाद-सुरक्षा

नोट (2019/08/30) : फ़ंक्शन तर्कों के मूल्यांकन क्रम में बदलाव के कारण C ++ 17 के बाद से यह कोई समस्या नहीं है। विशेष रूप से, फ़ंक्शन के प्रत्येक तर्क को अन्य तर्कों के मूल्यांकन से पहले पूरी तरह से निष्पादित करने की आवश्यकता होती है।

चूंकि ओपी चीजों के अपवाद-सुरक्षा पक्ष के बारे में सोच रहा है, इसलिए मैंने अपना उत्तर अपडेट कर दिया है।

इस उदाहरण पर विचार करें,

void F(const std::shared_ptr<Lhs> &lhs, const std::shared_ptr<Rhs> &rhs) { /* ... */ }

F(std::shared_ptr<Lhs>(new Lhs("foo")),
  std::shared_ptr<Rhs>(new Rhs("bar")));

चूँकि C ++, सबटेक्शंस के मूल्यांकन का मनमाना आदेश देता है, एक संभावित आदेश है:

  1. new Lhs("foo"))
  2. new Rhs("bar"))
  3. std::shared_ptr<Lhs>
  4. std::shared_ptr<Rhs>

अब, मान लीजिए कि हमें चरण 2 पर फेंका गया एक अपवाद मिलता है (उदाहरण के लिए, मेमोरी अपवाद से बाहर, Rhsकंस्ट्रक्टर ने कुछ अपवाद फेंक दिया)। हम फिर चरण 1 पर आवंटित स्मृति खो देते हैं, क्योंकि कुछ भी इसे साफ करने का मौका नहीं होता। यहाँ समस्या का मूल यह है कि कच्चे पॉइंटर को std::shared_ptrतुरंत कंस्ट्रक्टर को पास नहीं दिया गया ।

इसे ठीक करने का एक तरीका उन्हें अलग-अलग लाइनों पर करना है ताकि यह मध्यस्थता आदेश न हो सके।

auto lhs = std::shared_ptr<Lhs>(new Lhs("foo"));
auto rhs = std::shared_ptr<Rhs>(new Rhs("bar"));
F(lhs, rhs);

इस पाठ्यक्रम को हल करने का पसंदीदा तरीका std::make_sharedइसके बजाय उपयोग करना है।

F(std::make_shared<Lhs>("foo"), std::make_shared<Rhs>("bar"));

अद्यतन II: का नुकसान std::make_shared

केसी की टिप्पणियों का हवाला देते हुए :

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

क्यों के उदाहरण weak_ptrनियंत्रण ब्लॉक को जीवित रखते हैं?

यह weak_ptrनिर्धारित करने के लिए कि प्रबंधित वस्तु अभी भी वैध है (उदाहरण के लिए lock) के लिए एक रास्ता होना चाहिए । वे ऐसा करते हैं shared_ptrकि प्रबंधित ऑब्जेक्ट के मालिक s की संख्या की जांच करके , जिसे नियंत्रण ब्लॉक में संग्रहीत किया जाता है। इसका नतीजा यह है कि कंट्रोल ब्लॉक तब तक जीवित रहते हैं जब तक कि shared_ptrगिनती और weak_ptrगिनती दोनों हिट 0 नहीं हो जाती।

वापस std::make_shared

चूंकि std::make_sharedनियंत्रण ब्लॉक और प्रबंधित ऑब्जेक्ट दोनों के लिए एक ही ढेर-आवंटन होता है, इसलिए नियंत्रण ब्लॉक और प्रबंधित ऑब्जेक्ट के लिए मेमोरी को स्वतंत्र रूप से मुक्त करने का कोई तरीका नहीं है। हमें तब तक इंतजार करना चाहिए जब तक कि हम नियंत्रण ब्लॉक और प्रबंधित ऑब्जेक्ट दोनों को मुक्त नहीं कर सकते हैं, जो तब तक होता है जब तक कि कोई shared_ptrएस या weak_ptrएस जीवित नहीं होता है।

मान लीजिए कि हमने इसके बजाय नियंत्रण ब्लॉक और प्रबंधित ऑब्जेक्ट के माध्यम से newऔर shared_ptrनिर्माणकर्ता के लिए दो ढेर-आवंटन किए । फिर हम मेमोरी को प्रबंधित ऑब्जेक्ट (शायद पहले) के लिए मुक्त करते हैं जब कोई shared_ptrजीवित नहीं होते हैं , और नियंत्रण ब्लॉक (शायद बाद में) के लिए मेमोरी को मुक्त कर देते हैं जब कोई weak_ptrजीवित नहीं होता है।


53
यह एक अच्छा विचार है कि छोटे कोने के मामले का make_sharedभी उल्टा उल्लेख किया जाए : चूंकि केवल एक ही आवंटन है, इसलिए जब तक कि कंट्रोल ब्लॉक का उपयोग नहीं होता है, तब तक प्वाइंटर की मेमोरी को नहीं हटाया जा सकता है। A weak_ptrनियंत्रण ब्लॉक को अनिश्चित काल तक जीवित रख सकता है।
केसी

14
एक और, अधिक शैलीगत, बिंदु है: यदि आप उपयोग करते हैं make_sharedऔर make_uniqueलगातार करते हैं, तो आपके पास कच्चे पॉइंटर्स के मालिक नहीं होंगे, newजो कोड गंध के रूप में हर घटना का इलाज कर सकते हैं ।
फिलिप

6
यदि केवल एक है shared_ptr, और कोई weak_ptrएस नहीं है , reset()तो shared_ptrउदाहरण पर कॉल करने से नियंत्रण ब्लॉक हट जाएगा। लेकिन यह परवाह किए बिना है या क्या make_sharedइसका इस्तेमाल किया गया था। उपयोग करने make_sharedसे फर्क पड़ता है क्योंकि यह प्रबंधित ऑब्जेक्ट के लिए आवंटित मेमोरी के जीवन-समय को लम्बा कर सकता है । जब shared_ptrगिनती 0 से हिट होती है, तो प्रबंधित ऑब्जेक्ट के लिए विध्वंसक की परवाह किए बिना बुलाया जाता है make_shared, लेकिन इसकी मेमोरी को मुक्त करना केवल तभी किया जा सकता है यदि make_sharedइसका उपयोग नहीं किया गया था । आशा है कि यह इसे और अधिक स्पष्ट करता है।
मप्र

4
यह भी ध्यान देने योग्य होगा कि make_sared "We Know Where You Live" अनुकूलन का लाभ उठा सकता है जो नियंत्रण ब्लॉक को एक पॉइंटर छोटा होने की अनुमति देता है। (विवरण के लिए, Stephan T. Lavavej की GN2012 प्रस्तुति को लगभग 12 मिनट पर देखें।) make_sared इस तरह न केवल आवंटन से बचता है, बल्कि यह कुल स्मृति को भी आवंटित करता है।
KnowItAllWannabe

1
@ हन्नाखिल: क्या यह शायद वह है जो आप खोज रहे हैं ...? melpon.org/wandbox/permlink/b5EpsiSxDeEzcoinGH
मपार्क

26

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

इस दक्षता make_sharedके साथ new- साथ इसका उपयोग करने का मतलब है कि आपको बेहतर अपवाद सुरक्षा देने के साथ - साथ कच्चे पॉइंटर्स से निपटने की आवश्यकता नहीं है - ऑब्जेक्ट को आवंटित करने के बाद एक अपवाद को फेंकने की कोई संभावना नहीं है लेकिन इसे स्मार्ट पॉइंटर को सौंपने से पहले।


2
मैं आपकी पहली बात को सही ढंग से समझ गया। क्या आप अपवाद सुरक्षा के बारे में दूसरे बिंदु पर विस्तार से बता सकते हैं या कुछ लिंक दे सकते हैं?
अनूप बुचके

22

एक और मामला है जहां दोनों संभावनाएं अलग-अलग हैं, जो पहले से ही उल्लेख किए गए हैं: यदि आपको गैर-सार्वजनिक कंस्ट्रक्टर (संरक्षित या निजी) को कॉल करने की आवश्यकता है, तो make_sared इसे एक्सेस करने में सक्षम नहीं हो सकता है, जबकि नए कार्यों के साथ संस्करण ठीक है ।

class A
{
public:

    A(): val(0){}

    std::shared_ptr<A> createNext(){ return std::make_shared<A>(val+1); }
    // Invalid because make_shared needs to call A(int) **internally**

    std::shared_ptr<A> createNext(){ return std::shared_ptr<A>(new A(val+1)); }
    // Works fine because A(int) is called explicitly

private:

    int val;

    A(int v): val(v){}
};

मैं इस सटीक समस्या में भाग गया और उपयोग करने का निर्णय लिया new, अन्यथा मैं उपयोग कर लेता make_shared। यहाँ इस बारे में एक संबंधित प्रश्न है: stackoverflow.com/questions/8147027/…
जिग्लीपफ 3

6

यदि आपको साझा किए गए ऑब्जेक्ट पर विशेष मेमोरी संरेखण की आवश्यकता है, जिसे आप साझा किया गया है, तो आप Make_sared पर भरोसा नहीं कर सकते हैं, लेकिन मुझे लगता है कि इसका उपयोग न करने के बारे में केवल एक ही अच्छा कारण है।


2
एक दूसरी स्थिति जहां मेक_शेयर अनुचित है, जब आप एक कस्टम डीलेटर निर्दिष्ट करना चाहते हैं।
KnowItAllWannabe

5

मैं std :: make_sared के साथ एक समस्या देखता हूं, यह निजी / संरक्षित कंस्ट्रक्टरों का समर्थन नहीं करता है


3

Shared_ptr: दो ढेर आवंटन करता है

  1. नियंत्रण ब्लॉक (संदर्भ गणना)
  2. वस्तु का प्रबंधन किया जा रहा है

Make_shared: केवल एक हीप आवंटन का प्रदर्शन करता है

  1. नियंत्रण ब्लॉक और ऑब्जेक्ट डेटा।

0

आवंटन पर दक्षता और चिंता के समय के बारे में, मैंने नीचे यह सरल परीक्षण किया, मैंने इन दो तरीकों (एक समय में एक) के माध्यम से कई उदाहरण बनाए:

for (int k = 0 ; k < 30000000; ++k)
{
    // took more time than using new
    std::shared_ptr<int> foo = std::make_shared<int> (10);

    // was faster than using make_shared
    std::shared_ptr<int> foo2 = std::shared_ptr<int>(new int(10));
}

बात यह है, कि नए प्रयोग की तुलना में make_sared का उपयोग करने में दोहरा समय लगा। तो, नए का उपयोग करते हुए मेक_शेयर के उपयोग के बजाय दो ढेर आवंटन हैं। हो सकता है कि यह एक बेवकूफी भरी परीक्षा हो, लेकिन क्या यह नहीं दिखाता है कि नए प्रयोग करने की तुलना में मेक_शेयर का उपयोग करने में अधिक समय लगता है? बेशक, मैं केवल उपयोग किए गए समय के बारे में बात कर रहा हूं।


4
वह परीक्षा कुछ निरर्थक है। क्या अनुकूलन के साथ रिलीज कॉन्फ़िगरेशन में परीक्षण किया गया था? इसके अलावा आपके सभी आइटम तुरंत मुक्त हो जाते हैं इसलिए यह यथार्थवादी नहीं है।
Phil1970

0

मुझे लगता है कि mr mpark के उत्तर का अपवाद सुरक्षा हिस्सा अभी भी एक वैध चिंता का विषय है। जब इस तरह से एक share_ptr बनाते हैं: share_ptr <T> (नया T), नया T सफल हो सकता है, जबकि शेयर्ड_ptr का नियंत्रण ब्लॉक का आवंटन विफल हो सकता है। इस परिदृश्य में, नव आबंटित T लीक हो जाएगा, क्योंकि share_ptr में यह जानने का कोई तरीका नहीं है कि इसे इन-प्लेस बनाया गया था और इसे हटाना सुरक्षित है। या क्या मैं कुछ न कुछ भूल रहा हूं? मुझे नहीं लगता कि फ़ंक्शन पैरामीटर मूल्यांकन पर कठोर नियम किसी भी तरह से यहाँ मदद करते हैं ...

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