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


270

जब कोई फ़ंक्शन shared_ptr(बूस्ट या C ++ 11 STL से) लेता है , तो क्या आप इसे पास कर रहे हैं:

  • संदर्भ द्वारा: void foo(const shared_ptr<T>& p)

  • या मूल्य द्वारा: void foo(shared_ptr<T> p)?

मैं पहली विधि पसंद करूंगा क्योंकि मुझे संदेह है कि यह तेज़ होगा। लेकिन क्या यह वास्तव में इसके लायक है या कोई अतिरिक्त मुद्दे हैं?

क्या आप अपनी पसंद के कारण दे सकते हैं या यदि मामला है, तो आपको क्यों लगता है कि इससे कोई फर्क नहीं पड़ता।


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

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

तीसरी संभावना है कि
सीडी

@ जोहान्स: मैं किसी भी नकल / रेफ-गिनती से बचने के लिए इसे कॉन्स्ट-रेफरेंस द्वारा वापस करूंगा। तब तक, मैं सभी सदस्यों को कॉन्स्टिट्यूशन द्वारा वापस कर देता हूं जब तक कि वे आदिम नहीं हैं।
GManNickG

जवाबों:


229

इस सवाल पर C ++ और बियोंड 2011 में आस्क अस एनीथिंग सेशन के दौरान स्कॉट, आंद्रेई और हर्ब द्वारा चर्चा और जवाब दिया गया है । प्रदर्शन और शुद्धता पर 4:34 से देखें ।shared_ptr

कुछ ही समय में, मूल्य से पारित होने का कोई कारण नहीं है, जब तक कि लक्ष्य किसी वस्तु के स्वामित्व को साझा करना नहीं है (उदाहरण के लिए, विभिन्न डेटा संरचनाओं के बीच, या विभिन्न थ्रेड्स के बीच)।

जब तक कि आप ऊपर दिए गए टॉक वीडियो में स्कॉट मेयर्स द्वारा बताए अनुसार इसे स्थानांतरित नहीं कर सकते, लेकिन यह C ++ के वास्तविक संस्करण से संबंधित है जिसका आप उपयोग कर सकते हैं।

इस चर्चा का एक बड़ा अपडेट गोइंगनेटिव 2012 सम्मेलन के इंटरएक्टिव पैनल के दौरान हुआ है : हमसे कुछ भी पूछें! जो देखने लायक है, विशेष रूप से 22:50 से


5
लेकिन जैसा कि यहां दिखाया गया है कि यह मूल्य से गुज़रना सस्ता है: stackoverflow.com/a/12002668/128384 को ध्यान में नहीं रखा जाना चाहिए (कम से कम कंस्ट्रक्टर तर्कों आदि के लिए जहां साझा किए गए एक शेयर को यहां सदस्य बनाया जा सकता है) कक्षा)?

2
@stijn हां और नहीं। जब तक आप C + मानक के संस्करण को स्पष्ट नहीं करते हैं, तब तक यह Q & A आपके लिए अधूरा है। सामान्य कभी नहीं / हमेशा नियमों को फैलाना बहुत आसान है जो केवल भ्रामक हैं। जब तक, पाठक डेविड अब्राहम के लेख और संदर्भों से परिचित होने में समय लेते हैं, या पोस्टिंग वर्तमान सी ++ मानक को ध्यान में रखते हैं। तो, दोनों जवाब, मेरा और आपने जो इशारा किया, उसे पोस्ट करने का समय दिया गया है।
एमएलसकोट

1
" जब तक मल्टी-थ्रेडिंग नहीं है" नहीं, एमटी किसी भी तरह से विशेष नहीं है।
21

3
मैं पार्टी के लिए सुपर लेट हूं, लेकिन साझा करने के लिए मूल्य के आधार पर साझा करने का मेरा कारण यह है कि यह कोड को छोटा और सुंदर बनाता है। गंभीरता से। Value*छोटा और पठनीय है, लेकिन यह बुरा है, इसलिए अब मेरा कोड भर गया है const shared_ptr<Value>&और यह काफी कम पठनीय है और बस ... कम सुव्यवस्थित है। जो void Function(Value* v1, Value* v2, Value* v3)अब हुआ करता था void Function(const shared_ptr<Value>& v1, const shared_ptr<Value>& v2, const shared_ptr<Value>& v3), और लोग इसके साथ ठीक हैं?
एलेक्स

7
@ एलेक्स कॉमन प्रैक्टिस क्लास के ठीक बाद उपनाम (टाइपडेफ़्स) बना रहा है। आपके उदाहरण के लिए: class Value {...}; using ValuePtr = std::shared_ptr<Value>;तब आपका कार्य सरल हो जाता है: void Function(const ValuePtr& v1, const ValuePtr& v2, const ValuePtr& v3)और आपको अधिकतम प्रदर्शन प्राप्त होता है। यही कारण है कि आप C ++ का उपयोग करते हैं, है ना? :)
4LegsDrivenCat

91

यहाँ हर्ब सटर की ले है

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

दिशानिर्देश: व्यक्त करें कि एक फ़ंक्शन एक बाइ-वैल्यू के स्वामित्व को साझा करेगा और बाय-वैल्यू साझा_पार्ट पैरामीटर का उपयोग करके साझा करेगा।

दिशानिर्देश: गैर-कास्ट शेयर्ड_एप्ट्र और पैरामीटर का उपयोग केवल शेयर्ड_प्ट्र को संशोधित करने के लिए करें। यदि आप सुनिश्चित नहीं हैं कि आप एक प्रति ले सकते हैं या स्वामित्व साझा नहीं करेंगे, तो एक कास्ट शेयर्ड_प्ट्र और एक पैरामीटर के रूप में उपयोग करें; अन्यथा विजेट * का उपयोग करें (या यदि अशक्त नहीं है, तो एक विजेट और)।


3
सटर के लिंक के लिए धन्यवाद। यह एक उत्कृष्ट लेख है। मैं विजेट पर उससे असहमत हूं *, वैकल्पिक <विजेट &> पसंद करता है यदि C ++ 14 उपलब्ध है। विजेट * पुराने कोड से बहुत अस्पष्ट है।
15

3
विजेट * और विजेट और संभावनाओं के रूप में शामिल करने के लिए +1। केवल विस्तृत करने के लिए, विजेट पास करना * या विजेट और संभवतः सबसे अच्छा विकल्प है जब फ़ंक्शन सूचक ऑब्जेक्ट को स्वयं जांच / संशोधित नहीं कर रहा है। इंटरफ़ेस अधिक सामान्य है, क्योंकि इसके लिए किसी विशिष्ट पॉइंटर प्रकार की आवश्यकता नहीं होती है, और शेयर्ड_ptr संदर्भ गणना का प्रदर्शन मुद्दा चकमा दे जाता है।
tgnottingham

4
मुझे लगता है कि यह दूसरी गाइडलाइन की वजह से आज स्वीकृत जवाब होना चाहिए। यह स्पष्ट रूप से वर्तमान स्वीकृत उत्तर को अमान्य करता है, जो कहता है: मूल्य से गुजरने का कोई कारण नहीं है।
मलबे

63

व्यक्तिगत रूप से मैं एक constसंदर्भ का उपयोग करूंगा । एक फ़ंक्शन कॉल के लिए इसे फिर से डीग्रेड करने के लिए केवल संदर्भ संख्या को बढ़ाने की आवश्यकता नहीं है।


1
मैंने आपके उत्तर को डाउन-वोट नहीं किया, लेकिन इससे पहले कि यह वरीयता का मामला है, विचार करने की दो संभावनाओं में से प्रत्येक के लिए पेशेवरों और विपक्ष हैं। और यह जानने के लिए अच्छा होगा कि शोध के पेशेवरों और विपक्षों पर चर्चा करें। बाद में हर कोई अपने लिए निर्णय ले सकता है।
दानविल

@ डनविल: कैसे shared_ptrकाम करता है, इस बात को ध्यान में रखते हुए , केवल संदर्भ के माध्यम से नहीं गुजरने की संभावना है, प्रदर्शन में मामूली नुकसान। यहां दो कारण हैं। a) पॉइंटर एलियासिंग फीचर का अर्थ है डेटा के लायक पॉइंट टू प्लस (एक काउंटर के लिए कमजोर रीफ्स) को कॉपी किया जाता है, इसलिए डेटा राउंड को कॉपी करना थोड़ा अधिक महंगा है। बी) परमाणु संदर्भ गिनती सादे पुराने वेतन वृद्धि / वृद्धि कोड की तुलना में थोड़ी धीमी है, लेकिन धागा सुरक्षित होने के लिए आवश्यक है। इसके अलावा, दो तरीके अधिकांश इरादों और उद्देश्यों के लिए समान हैं।
इवान टेरान

37

constसंदर्भ से गुजरें, यह तेज है। यदि आपको इसे स्टोर करने की आवश्यकता है, तो कुछ कंटेनर में कहें, रेफ। गिनती ऑटो-जादुई रूप से कॉपी ऑपरेशन द्वारा बढ़ाई जाएगी।


4
डाउनवोट इसे वापस करने के लिए किसी भी संख्या के बिना अपनी राय का निर्गमन करें।
kwesolowski

22

मैं एक बार के साथ नीचे कोड भाग गया, fooलेने shared_ptrसे const&और फिर से साथ fooले जा रहा shared_ptrमूल्य द्वारा।

void foo(const std::shared_ptr<int>& p)
{
    static int x = 0;
    *p = ++x;
}

int main()
{
    auto p = std::make_shared<int>();
    auto start = clock();
    for (int i = 0; i < 10000000; ++i)
    {
        foo(p);
    }    
    std::cout << "Took " << clock() - start << " ms" << std::endl;
}

मेरे इंटेल कोर 2 क्वाड (2.4GHz) प्रोसेसर पर VS2015, x86 रिलीज़ बिल्ड का उपयोग करना

const shared_ptr&     - 10ms  
shared_ptr            - 281ms 

मूल्य संस्करण द्वारा प्रतिलिपि परिमाण धीमी का एक आदेश था।
यदि आप किसी फ़ंक्शन को वर्तमान थ्रेड से सिंक्रोनाइज़ कर रहे हैं, तो const&संस्करण को प्राथमिकता दें ।


1
क्या आप कह सकते हैं कि आपने कौन सा कंपाइलर, प्लेटफ़ॉर्म और ऑप्टिमाइज़ेशन सेटिंग्स का उपयोग किया है?
कार्लटन

मैंने vs2015 के डिबग बिल्ड का उपयोग किया, अब रिलीज़ बिल्ड का उपयोग करने का उत्तर अपडेट किया।
tcb

1
मैं उत्सुक हूँ कि जब अनुकूलन चालू होता है, तो आप दोनों के साथ समान परिणाम प्राप्त करते हैं
इलियट वुड्स

2
अनुकूलन ज्यादा मदद नहीं करता है। समस्या कॉपी पर संदर्भ गणना पर ताला विवाद है।
एलेक्स

1
ये मुद्दा नहीं है। इस तरह के foo()फ़ंक्शन को पहली बार साझा किए गए पॉइंटर को भी स्वीकार नहीं करना चाहिए क्योंकि यह इस ऑब्जेक्ट का उपयोग नहीं कर रहा है: इसे कॉल से int&और ए को स्वीकार करना चाहिए । एक फ़ंक्शन एक स्मार्ट पॉइंटर ऑब्जेक्ट को स्वीकार करता है जब उसे इसके साथ कुछ करने की आवश्यकता होती है, और अधिकांश समय, आपको जो करने की आवश्यकता होती है वह इसे ( ) कहीं और ले जा रहा है, इसलिए एक उप-मान पैरामीटर की कोई लागत नहीं है। p = ++x;foo(*p);main()std::move()
eepp

14

C ++ 11 के बाद से आपको इसे कॉन्स्ट मूल्य पर लेना चाहिए और जितना आप सोच सकते हैं, उससे अधिक बार।

यदि आप std :: share_ptr (अंतर्निहित प्रकार T के बजाय) ले रहे हैं, तो आप ऐसा इसलिए कर रहे हैं क्योंकि आप इसके साथ कुछ करना चाहते हैं।

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

बेशक, अगर फोन करने वाले को दोनों के लिए अपने शेयर्ड_पार्ट की आवश्यकता लंबे समय तक रहती है (इस प्रकार std :: इसे स्थानांतरित नहीं कर सकते हैं) और आप फ़ंक्शन में एक सादा कॉपी नहीं बनाना चाहते (कहते हैं कि आप एक कमजोर सूचक चाहते हैं, या आप केवल कभी-कभी चाहते हैं इसे कॉपी करने के लिए, कुछ शर्त के आधार पर), फिर एक कास्ट और अभी भी बेहतर हो सकता है।

उदाहरण के लिए, आपको करना चाहिए

void enqueue(std::shared<T> t) m_internal_queue.enqueue(std::move(t));

ऊपर

void enqueue(std::shared<T> const& t) m_internal_queue.enqueue(t);

क्योंकि इस मामले में आप हमेशा आंतरिक रूप से एक प्रतिलिपि बनाते हैं


1

शेयर्ड_कॉपी कॉपी ऑपरेशन की समय लागत ज्ञात नहीं है जहां परमाणु वृद्धि और क्षरण होता है, मैं सीपीयू उपयोग की बहुत अधिक समस्या से ग्रस्त था। मैंने कभी उम्मीद नहीं की थी कि परमाणु वेतन वृद्धि और गिरावट इतनी लागत ले सकती है।

मेरे परीक्षण के परिणाम के बाद, int32 परमाणु वेतन वृद्धि और गिरावट गैर-परमाणु वेतन वृद्धि और वेतन वृद्धि की तुलना में 2 या 40 गुना अधिक है। मुझे यह विंडोज 8.1 के साथ 3GHz Core i7 पर मिला है। पूर्व परिणाम तब सामने आता है जब कोई विवाद नहीं होता है, बाद में जब विवाद की उच्च संभावना होती है। मैं यह ध्यान रखता हूं कि परमाणु संचालन अंतिम हार्डवेयर आधारित लॉक पर होता है। ताला बंद है। विवाद होने पर प्रदर्शन के लिए बुरा।

यह अनुभव करते हुए, मैं हमेशा byref (साझा_ptr) की तुलना में byref (const साझा_ptr &) का उपयोग करता हूं।


0

एक हालिया ब्लॉग पोस्ट था: https://medium.com/@vgasparyan1995/pass-by-value-vs-pass-by-reference-to-const-c-f8944171e3ce

तो इसका उत्तर यह है: करो (लगभग) कभी पास नहीं होते const shared_ptr<T>&
इसके बजाय केवल अंतर्निहित वर्ग पास करें।

मूल रूप से केवल उचित पैरामीटर प्रकार हैं:

  • shared_ptr<T> - संशोधित करें और स्वामित्व लें
  • shared_ptr<const T> - संशोधित मत करो, स्वामित्व ले लो
  • T& - संशोधित करें, कोई स्वामित्व नहीं
  • const T& - संशोधित न करें, कोई स्वामित्व नहीं
  • T - कॉपी करने के लिए सस्ता, कोई स्वामित्व नहीं

जैसा कि @accel ने https://stackoverflow.com/a/26197326/1930508 में बताया है कि हर्ब स्पटर की सलाह है:

यदि आप सुनिश्चित नहीं हैं कि आप एक प्रति लेंगे और स्वामित्व साझा करेंगे या नहीं, तो एक कास्ट शेयर्ड_प्ट्र और एक पैरामीटर के रूप में उपयोग करें

लेकिन कितने मामलों में आप सुनिश्चित नहीं हैं? तो यह एक दुर्लभ स्थिति है


0

यह ज्ञात समस्या है कि साझा मूल्य से साझा करने की लागत है और यदि संभव हो तो इसे टाला जाना चाहिए।

साझा करने की लागत

अधिकांश समय संदर्भ के द्वारा साझा किया जाता है, और कॉन्स्ट रेफरेंस द्वारा बेहतर होता है।

Cpp कोर गाइडलाइन के पास साझा करने के लिए एक विशिष्ट नियम है

R.34: एक फंक्शन पार्ट पार्ट के मालिक को व्यक्त करने के लिए शेयर्ड_प्ट्र पैरामीटर लें

void share(shared_ptr<widget>);            // share -- "will" retain refcount

जब वैल्यू द्वारा शेयर्ड_एप्ट को पास किया जाता है, तो एक उदाहरण वास्तव में आवश्यक होता है जब कॉल करने वाला किसी ऑब्जेक्ट को एक एसिंक्रोनस केली के पास भेजता है - यानी कॉल करने वाले के दायरे से बाहर जाने से पहले ही कैली पूरी हो जाती है। कैलली को मूल्य द्वारा share_ptr लेकर साझा किए गए ऑब्जेक्ट के जीवनकाल का "विस्तार" करना चाहिए। इस मामले में, share_ptr के संदर्भ को पास नहीं किया जाएगा।

एक साझा ऑब्जेक्ट को एक कार्य थ्रेड में पारित करने के लिए वही जाता है।


-4

share_ptr काफी बड़ा नहीं है, और न ही इसके कंस्ट्रक्टर \ destructor के लिए पर्याप्त काम करते हैं कॉपी से पर्याप्त ओवरहेड होने के बारे में परवाह करने के लिए संदर्भ बनाम पास से कॉपी प्रदर्शन।


15
क्या आपने इसे मापा है?
जिज्ञासु

2
@stonemalal: नए शेयर्ड_एप्ट्र बनाने के दौरान परमाणु निर्देशों के बारे में क्या?
क्वरा

यह एक गैर-पीओडी प्रकार है, इसलिए अधिकांश एबीआई में भी इसे "मूल्य से" पास करना वास्तव में एक सूचक है। यह बाइट्स की वास्तविक नकल नहीं है जो कि मुद्दा है। जैसा कि आप asm आउटपुट में देख सकते हैं, shared_ptr<int>मान से गुजरते हुए 100 x86 निर्देश लेता है (महंगे lockएड निर्देशों को एटमॉली इंक / रेफरी काउंट सहित)। निरंतर रेफ़र द्वारा पासिंग किसी भी चीज़ के लिए एक पॉइंटर को पास करने के समान है (और गॉडबोल्ट कंपाइलर एक्सप्लोरर पर इस उदाहरण में, टेल-कॉल ऑप्टिमाइज़ेशन कॉल के बजाय एक साधारण जेएमपी में बदल जाता है: Godbolt.org/g/TazMBU )।
पीटर कॉर्ड्स

TL: DR: यह C ++ है जहां कॉपी-कंस्ट्रक्टर केवल बाइट्स कॉपी करने की तुलना में बहुत अधिक काम कर सकते हैं। यह उत्तर कुल कचरा है।
पीटर कॉर्ड्स

2
stackoverflow.com/questions/3628081/sared-ptr-horprise-speed एक उदाहरण के रूप में साझा किए गए संकेत मान बनाम पास से गुजरते हैं वह लगभग 33% का रन टाइम अंतर देखता है। अगर आप परफॉर्मेंस क्रिटिकल कोड पर काम कर रहे हैं तो न्यूड पॉइंटर्स से आपको बड़ी परफॉर्मेंस बढ़ सकती है। अगर आपको याद है तो कॉन्स्ट रेफरी द्वारा पास जरूर करें लेकिन अगर आप नहीं करते हैं तो यह बहुत बड़ी बात नहीं है। यदि आपको इसकी आवश्यकता नहीं है, तो इसे साझा करने के लिए साझा करना महत्वपूर्ण नहीं है।
२०:३० पर स्टोनेमेटल
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.