2012-05-08 15 views
10

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

  1. क्या किसी को पता है कि यह एक अच्छा डिफ़ॉल्ट व्यवहार क्यों करता है? Esp। सी ++ 11 में रैवल्यू संदर्भ और सही अग्रेषण के साथ, ऐसा लगता है कि यह तर्कों को पूरी तरह से आगे बढ़ाने के लिए और अधिक समझ में आता है।

  2. std :: make_shared हालांकि प्रतिलिपि/स्थानांतरित नहीं होता है लेकिन केवल प्रदान किए गए तर्कों को पूरी तरह से आगे बढ़ाता है। यहां तर्कों को अग्रेषित करने के दो अलग-अलग व्यवहार क्यों हैं? (Std :: धागा और std :: बाँध कि हमेशा कॉपी/स्थानांतरित std :: make_shared कि नहीं बनाम)

+0

यदि आप किसी स्थानीय के संदर्भ को बाध्य करते हैं, तो आप परेशानी में होंगे। – mfontanini

+1

मैं नहीं होगा अगर बुलाया गया कार्य स्थानीय के दायरे से बाहर नहीं है। सी ++ में बहुत सी चीजें हैं जो आपको अपना पैर उड़ाने देती हैं। मेरा सवाल इस बिंदु पर अधिक है कि यह व्यवहार कम अंतर्ज्ञानी प्रतीत होता है। – ryaner

+0

किसी बिंदु पर मेरे उत्तर में "और वह स्थानीय दायरे से बाहर चला गया" है, लेकिन स्पष्ट रूप से यह संपादन के बाद मिटा दिया गया है: एस – mfontanini

उत्तर

13

make_shared आगे एक कन्स्ट्रक्टर को कहा जा रहा है जिसे कहा जा रहा है। यदि निर्माता संदर्भ अर्थशास्त्र द्वारा कॉल का उपयोग करता है, तो यह संदर्भ प्राप्त करेगा; यदि यह मूल्य से कॉल करता है, तो यह एक प्रतिलिपि बना देगा। यहां कोई समस्या नहीं है।

bind भविष्य में कुछ अज्ञात बिंदुओं पर कॉल किए जाने वाले फ़ंक्शन को देरी कॉल बनाता है, जब स्थानीय संदर्भ संभावित रूप से चला जाता है। यदि bind सही अग्रेषण का उपयोग कर रहे थे, तो आपको उन तर्कों की प्रतिलिपि बनाना होगा जिन्हें सामान्य रूप से भेजा जाता है और वास्तविक कॉल के समय रहने के लिए ज्ञात नहीं है, उन्हें कहीं स्टोर करें, और उस संग्रहण को प्रबंधित करें। वर्तमान semantics bind के साथ यह आपके लिए करता है।

2

सबसे संभावित कारण बस जो C++ है काफी हर जगह डिफ़ॉल्ट रूप से मूल्य अर्थ विज्ञान का उपयोग करता है। और संदर्भों का उपयोग आसानी से निर्दिष्ट वस्तु के जीवनकाल से संबंधित मुद्दों को बना सकता है।

+0

लेकिन क्या यह सी ++ का सिद्धांत नहीं है कि प्रोग्रामर जानता है कि यह सबसे अच्छा क्या चाहता है? संदर्भ को लटकने के मामले में, प्रोग्रामर को सावधान रहना चाहिए कि उसके फ़ंक्शन के पैरामीटर मूल्य के हिसाब से मूल्यवान होंगे, और जब हमारे फ़ंक्शन का पैरामीटर संदर्भ लेता है, तो मुझे उम्मीद है कि चीजें पूरी तरह से किसी भी जोखिम के संदर्भ के रूप में अग्रेषित की जाएंगी। सिर्फ एक विचार। – ryaner

+1

नहीं। सिद्धांत यह है कि प्रोग्रामर उन सुविधाओं के प्रदर्शन में भुगतान नहीं करता है जिनका वह उपयोग नहीं करता है। "पता है कि आप क्या कर रहे हैं" चीज केवल इस अंत को प्राप्त करने का मतलब है, अंत में और अंत में नहीं। –

+1

सी ++ में बहुत सारे सिद्धांत हैं :-)। सबसे मौलिक लोगों में से एक यह है कि यह डिफ़ॉल्ट रूप से मूल्य semantics का उपयोग करता है; अन्यथा ऐसा करने के लिए आपको स्पष्ट रूप से एक संदर्भ या सूचक का अनुरोध करना होगा। (बेशक, मानक पुस्तकालय हमेशा इस नियम का पालन नहीं करता है --- इटरेटर और भविष्यवाणियां हमेशा मूल्य से होती हैं, लेकिन अन्य चीजें अलग-अलग होती हैं।) –

1

std :: बाइंड एक कॉल करने योग्य बनाता है जो std::bind की कॉल साइट से अलग है, इस प्रकार यह डिफ़ॉल्ट रूप से सभी तर्कों को मूल्य से कैप्चर करने के लिए बहुत अधिक समझ में आता है।

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

लैम्ब्डा प्रोग्रामर के लिए यह तय करने के लिए अधिक लचीलापन देता है कि लैम्ब्डा दायरे के तर्क से परे रहेंगे या नहीं।

+0

क्या यह std :: thread के लिए एक ही मामला है? थ्रेड स्टार्ट फ़ंक्शन के मामले में यह कम संभावना है कि इसे लैम्ब्डा के रूप में लिखा जाएगा। और संदर्भ द्वारा पारित होने के लिए लैम्ब्डा के बंद होने का उपयोग करने के लिए मजबूर होना अजीब लग सकता है? Lambdas इस वजह से सभी समस्याओं को हल करने के लिए प्रतीत नहीं होता .. मुझे लगता है? – ryaner

+0

पहली वाक्य एक अच्छा कारण देता है। वोट दिया धन्यवाद। – ryaner

7

std::bind और std::thread दोनों के लिए, दिए गए तर्कों पर फ़ंक्शन का आमंत्रण कॉल साइट से स्थगित कर दिया गया है। दोनों मामलों में, जब फ़ंक्शन को कॉल किया जाएगा तो बस अज्ञात है।

इस तरह के मामले में सीधे पैरामीटर को आगे बढ़ाने के लिए संदर्भों को संग्रहीत करने की आवश्यकता होगी। जिसका अर्थ हो सकता है स्टैक ऑब्जेक्ट्स के संदर्भों को संग्रहीत करना। कॉल वास्तव में निष्पादित होने पर कौन सा अस्तित्व में नहीं हो सकता है।

ओह।

लैम्बडास ऐसा कर सकता है क्योंकि आपको प्रति-कैप्चर आधार पर निर्णय लेने की क्षमता दी जाती है, चाहे आप संदर्भ या मूल्य से कैप्चर करना चाहते हों। std::ref का उपयोग करके, आप संदर्भ द्वारा पैरामीटर बाध्य कर सकते हैं।

+0

समझ में आता है। वोट दिया! अगर मुझे दो उत्तरों को स्वीकार करने की अनुमति दी गई तो स्वीकार करेंगे। धन्यवाद। – ryaner

1

मैंने वास्तव में एक छोटी उपयोगिता लिख ​​दी जो देरी से इनवोकेशन फ़ैक्टर (कुछ हद तक std::bind-जैसा बनाता है, लेकिन नेस्टेड बाइंड एक्सप्रेशन/प्लेसहोल्डर्स फीचर्स के बिना)।मेरा मुख्य प्रेरणा इस मामले कि मैंने पाया था जवाबी सहज ज्ञान युक्त:

using pointer_type = std::unique_ptr<int>; 
pointer_type source(); 
void sink(pointer_type p); 

pointer_type p = source(); 

// Either not valid now or later when calling bound() 
// auto bound = std::bind(sink, std::move(p)); 
auto bound = std::bind(
    [](pointer_type& p) { sink(std::move(p)); } 
    , std::move(p)); 
bound(); 

कि एडाप्टर के लिए कारण (जो sink करने के लिए अपने lvalue रेफरी तर्क चलता है) कि std::bind द्वारा कॉल आवरण वापसी हमेशा lvalues ​​के रूप में बाध्य तर्क अग्रेषित करता है । यह समस्या के साथ कोई समस्या नहीं थी C12+ 03 में boost::bind क्योंकि उस लवली या तो अंतर्निहित कॉल करने योग्य ऑब्जेक्ट के संदर्भ तर्क या प्रतिलिपि के माध्यम से मान तर्क के साथ बाध्य होगा। pointer_type के बाद से यहां काम नहीं करता है केवल स्थानांतरित है। कैसे बाध्य तर्क संग्रहीत होना चाहिए, और वे कैसे बहाल होना चाहिए (अर्थात प्रतिदेय वस्तु को पारित कर दिया):

अंतर्दृष्टि है कि मुझे मिल गया वास्तव में दो बातों पर विचार करने के लिए देखते हैं कि है। नियंत्रण जो std::bind अनुदान देता है, आप निम्नानुसार हैं: तर्क या तो उथले (std::ref के उपयोग के माध्यम से) या नियमित तरीके से (std::decay का उपयोग करके आगे बढ़ते हैं); वे हमेशा लालच के रूप में बहाल होते हैं (सीवी-क्वालीफायर के साथ स्वयं कॉल कॉलर से विरासत में मिला)। सिवाय इसके कि आप बाद वाले को एक छोटे से साइट एडाप्टर लैम्ब्डा अभिव्यक्ति के साथ बाईपास कर सकते हैं जैसा मैंने अभी किया था।

यह तर्कसंगत रूप से बहुत कम नियंत्रण और सीखने के लिए अपेक्षाकृत बहुत अभिव्यक्ति है। तुलनात्मक रूप से मेरी उपयोगिता में bind(f, p) (क्षय और स्टोर प्रति, lvalue के रूप में बहाल), bind(f, ref(p)) (उथले रूप से स्टोर, lvalue के रूप में बहाल), bind(f, std::move(p)) (चाल से क्षय और स्टोर, रैवल्यू के रूप में बहाल), bind(f, emplace(p)) (चाल से क्षय और स्टोर, lvalue के रूप में बहाल)। यह एक ईडीएसएल सीखने की तरह लगता है।

+0

यह एक बहुत ही रोचक विचार है, क्या आप हमें कार्यान्वयन के लिए इंगित कर सकते हैं ताकि हम इसका पता लगा सकें? इसके अलावा, परिणामी मज़ेदार तार्किक रूप से केवल एक बार बुलाया जा सकता है? और क्या हुआ अगर हम परिणामी मज़ेदार की प्रतिलिपि बनाते हैं? – authchir

+0

@Authchir [यहां कोड] (https://bitbucket.org/mickk/annex/src/8166e2d92508/include/annex/make_invoke.hpp) (और 'emplace' जैसी चीजें यहां से हैं [https: // bitbucket .org/mickk/चयक/src/8166e2d92508/शामिल/चयक/val.hpp))। आप सही हैं कि एक बार मज़ेदार को कॉल करने के लिए केवल सही है (आगे एक खाली 'पॉइंटर_ टाइप' को आगे कॉल करें, और इसके अलावा कॉल रैपर केवल स्थानांतरित है।यह सब समकक्ष 'std :: bind' उपयोग पर भी लागू होता है। –

+0

इसके अलावा [यूनिट टेस्ट] (https://bitbucket.org/mickk/annex/src/8166e2d92508/unit/make_invoke.cpp) सामान्य उपयोग दिखाता है। –

संबंधित मुद्दे