2013-06-04 7 views
30

एक लैम्ब्डा पासिंग C++ 11 वास्तव में आसान है:सी ++ 11 लैम्बडा कैसे प्रतिनिधित्व और पारित होते हैं?

func([](int arg) { 
    // code 
}) ; 

लेकिन मैं सोच रहा हूँ, इस तरह एक समारोह के लिए एक लैम्ब्डा गुजर की लागत क्या है? क्या होगा अगर func lambda को अन्य कार्यों में पास करता है?

void func(function< void (int arg) > f) { 
    doSomethingElse(f) ; 
} 

लैम्ब्डा महंगा है? के बाद से एक function वस्तु 0 सौंपा जा सकता है,

function< void (int arg) > f = 0 ; // 0 means "not init" 

यह मुझे ले जाता है कि समारोह वस्तुओं संकेत की तरह कार्य की तरह लगता है। लेकिन new के उपयोग के बिना, इसका मतलब है कि वे मूल्य-टाइप struct या कक्षाओं की तरह हो सकते हैं, जो आवंटन और सदस्य-वार प्रतिलिपि को रोकने के लिए डिफ़ॉल्ट हैं।

एक सी ++ 11 "कोड बॉडी" कैप्चर किए गए चर के समूह को कैसे पास किया जाता है जब आप "मूल्य से" फ़ंक्शन ऑब्जेक्ट पास करते हैं? क्या कोड निकाय की बहुत अधिक प्रतिलिपि है?

void func(const function< void (int arg) >& f) { 
} 

या समारोह करना किसी भी तरह नियमित रूप से सी ++ structs तुलना में अलग तरह से पारित वस्तुओं: मैं प्रत्येक function वस्तु const& साथ पारित कर दिया है, ताकि एक प्रति नहीं किया जाता है चिह्नित करने के लिए होना चाहिए?

+0

अच्छा सवाल है, यह भी जानना चाहते :) – Xaqq

उत्तर

30

अस्वीकरण: मेरा उत्तर वास्तविकता की तुलना में कुछ हद तक सरलीकृत है (मैंने कुछ विवरण अलग रखा है) लेकिन बड़ी तस्वीर यहां है। साथ ही, मानक पूरी तरह से निर्दिष्ट नहीं करता है कि कैसे lambdas या std::function आंतरिक रूप से कार्यान्वित किया जाना चाहिए (कार्यान्वयन में कुछ स्वतंत्रता है) ताकि कार्यान्वयन के विवरण पर किसी भी चर्चा की तरह, आपका कंपाइलर इस तरह से ऐसा कर सकता है या नहीं।

लेकिन फिर, यह एक विषय VTables के समान ही है: मानक को अधिक जरूरी नहीं है लेकिन किसी भी समझदार कंपाइलर को अभी भी ऐसा करने की संभावना है, इसलिए मेरा मानना ​​है कि इसमें थोड़ा खोदना उचित है।

auto lambda = [](Args...) -> Return { /*...*/ }; 

// roughly equivalent to: 
struct { 
    Return operator()(Args...) { /*...*/ } 
} 
lambda; // instance of the anonymous struct 
बस किसी भी अन्य वर्ग की तरह

, जब आप अपने चारों ओर अपने उदाहरणों पारित कभी नहीं: :)


lambdas

एक लैम्ब्डा लागू करने के लिए सबसे सरल तरीका एक गुमनाम struct की तरह है कोड को प्रतिलिपि बनाना है, केवल वास्तविक डेटा (यहां, बिल्कुल भी नहीं)।


वस्तुओं मूल्य द्वारा कब्जा कर लिया struct में नकल कर रहे हैं:

Value v; 
auto lambda = [=](Args...) -> Return { /*... use v, captured by value...*/ }; 

// roughly equivalent to: 
struct Temporary { // note: we can't make it an anonymous struct any more since we need 
        // a constructor, but that's just a syntax quirk 

    const Value v; // note: capture by value is const by default unless the lambda is mutable 
    Temporary(Value v_) : v(v_) {} 
    Return operator()(Args...) { /*... use v, captured by value...*/ } 
} 
lambda(v); // instance of the struct 

फिर, उसके चारों ओर गुजर केवल मतलब है कि आप डेटा (v) नहीं कोड में ही गुजरती हैं।


इसी तरह, वस्तुओं संदर्भ द्वारा कब्जा कर लिया struct में संदर्भित हैं:

Value v; 
auto lambda = [&](Args...) -> Return { /*... use v, captured by reference...*/ }; 

// roughly equivalent to: 
struct Temporary { 
    Value& v; // note: capture by reference is non-const 
    Temporary(Value& v_) : v(v_) {} 
    Return operator()(Args...) { /*... use v, captured by reference...*/ } 
} 
lambda(v); // instance of the struct 

कि काफी सब है जब यह खुद को lambdas के लिए (कुछ कार्यान्वयन विवरण मैं लोप को छोड़कर आता है, लेकिन जो प्रासंगिक नहीं हैं यह समझने के लिए कि यह कैसे काम करता है)।


std::function

std::function functor (lambdas, स्टैंडअलोन/स्थिर/सदस्य काम करता है, लोगों की तरह functor वर्गों मैं से पता चला है, ...) के किसी भी प्रकार के चारों ओर एक सामान्य आवरण है।

std::function के आंतरिक बहुत जटिल हैं क्योंकि उन्हें उन सभी मामलों का समर्थन करना होगा। functor का सही प्रकार पर निर्भर करता है इस की आवश्यकता है कम से कम निम्नलिखित डेटा (दे या कार्यान्वयन विवरण लेने के लिए):

  • एक स्टैंडअलोन/स्थिर कार्य करने के लिए एक सूचक।

या,

  • एक प्रति करने के लिए एक सूचक functor की (गतिशील functor किसी भी प्रकार की अनुमति देने के लिए आवंटित, जैसा कि आप ठीक ही यह ध्यान दिया) [नीचे नोट देखें]।
  • सदस्य समारोह के लिए एक सूचक कहा जाता है।
  • एक आवंटक के लिए एक सूचक जो दोनों को मज़ेदार और खुद की प्रतिलिपि बनाने में सक्षम है (चूंकि किसी भी प्रकार के मज़ेदार का उपयोग किया जा सकता है, इसलिए पॉइंटर-टू-फ़ैक्टर void* होना चाहिए और इस प्रकार ऐसा तंत्र होना चाहिए - शायद polymorphism उर्फ। बेस क्लास + आभासी तरीकों, व्युत्पन्न वर्ग template<class Functor> function(Functor) रचनाकारों में स्थानीय रूप से उत्पन्न किया जा रहा है)।

चूंकि यह पहले से पता है नहीं functor किस तरह यह स्टोर करने के लिए होगा (और इस तथ्य यह है कि std::function पुन: असाइन किया जा सकता द्वारा स्पष्ट किया जाता है) तो यह सब संभव मामलों से निपटने के लिए है और यह फैसला लेने के लिए चलने के समय पर।

नोट: मैं जहां स्टैंडर्ड जनादेश यह पता नहीं है, लेकिन यह निश्चित रूप से एक नई प्रतिलिपि है, अंतर्निहित functor साझा नहीं है:

int v = 0; 
std::function<void()> f = [=]() mutable { std::cout << v++ << std::endl; }; 
std::function<void()> g = f; 

f(); // 0 
f(); // 1 
g(); // 0 
g(); // 1 

इसलिए, जब आप एक std::function आसपास पारित इसमें कम से कम उन चार पॉइंटर्स शामिल हैं (और वास्तव में जीसीसी 4.7 64 बिट्स sizeof(std::function<void()> 32 है जो चार 64 बिट पॉइंटर्स हैं) और वैकल्पिक रूप से मज़ेदार की गतिशील रूप से आवंटित प्रति (जिसे मैंने पहले ही कहा है, में केवल कब्जे वाले ऑब्जेक्ट्स हैं, आप कोड कॉपी न करें)।


सवाल

इस तरह एक समारोह के लिए एक लैम्ब्डा गुजर की लागत क्या है करने के लिए उत्तर?[प्रश्न के संदर्भ: मूल्य द्वारा]

ठीक है, आप इसे अपने functor पर मुख्य रूप से निर्भर करता है (या तो एक हाथ से बनाई गई struct functor या एक लैम्ब्डा) और चर इसमें देख सकते हैं। ओवरहेड सीधे struct फ़ैक्टर द्वारा मूल्य से गुजरने की तुलना में काफी नगण्य है, लेकिन यह संदर्भ के अनुसार struct फ़ैक्टर को पार करने से काफी अधिक है।

मैं प्रत्येक कार्य वस्तु const& के साथ पारित चिह्नित करने के लिए इतना है कि एक प्रति नहीं किया जाता है है चाहिए?

मुझे डर है कि एक सामान्य तरीके से जवाब देना बहुत मुश्किल है। कभी-कभी आप const संदर्भ से गुजरना चाहते हैं, कभी-कभी मूल्य से, कभी-कभी रैवल्यू संदर्भ से ताकि आप इसे स्थानांतरित कर सकें। यह वास्तव में आपके कोड के अर्थशास्त्र पर निर्भर करता है।

नियम जो आपको चुनना चाहिए, वह एक बिल्कुल अलग विषय आईएमओ है, बस याद रखें कि वे किसी अन्य वस्तु के समान हैं।

वैसे भी, अब आपके पास एक सूचित निर्णय लेने के लिए सभी कुंजी हैं (फिर, आपके कोड और इसके अर्थशास्त्र के आधार पर)।

1

यदि लैम्ब्डा को एक साधारण कार्य के रूप में बनाया जा सकता है (यानी यह कुछ भी कैप्चर नहीं करता है), तो यह बिल्कुल वैसे ही बनाया जाता है। विशेष रूप से मानक के रूप में यह एक ही हस्ताक्षर के साथ पुराने शैली सूचक-टू-फ़ंक्शन के साथ संगत होने की आवश्यकता है। [संपादित करें: यह सटीक नहीं है, टिप्पणियों में चर्चा देखें]

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

+0

"* मानक यह एक ही साथ पुरानी शैली सूचक-टू-समारोह के साथ संगत होना करने के लिए की आवश्यकता है चाहते हैं हस्ताक्षर * "=> उद्धरण की देखभाल? मुझे इसके बारे में पता नहीं था (जो स्वयं ही आश्चर्यजनक नहीं है)। – syam

+0

कभी नहीं मिला, यह पाया गया: 5.1.2-6 * लैम्ब्डा-कैप्चर के साथ लैम्ब्डा-अभिव्यक्ति के बंद होने का प्रकार एक गैर-वर्चुअल गैर-स्पष्ट कॉन्स्ट रूपांतरण फ़ंक्शन होता है जिसमें पॉइंटर को समान पैरामीटर और रिटर्न प्रकार होते हैं बंद करने का प्रकार का फ़ंक्शन कॉल ऑपरेटर।इस रूपांतरण फ़ंक्शन द्वारा लौटाए गए मान को उस फ़ंक्शन का पता दिया जाएगा, जब लागू किया जाता है, तो बंद करने के प्रकार के फ़ंक्शन कॉल ऑपरेटर को आविष्कार करने के समान प्रभाव होता है। * ==> एक गैर-कैप्चरिंग लैम्ब्डा को स्टैंडअलोन के रूप में लागू करने की आवश्यकता नहीं होती है कार्य, इसे केवल एक रूपांतरण समारोह प्रदान करना है। – syam

+0

उम्मीद है कि मैंने जो कहा है उसके विपरीत नहीं है ;-) –

3

भी देखें C++11 lambda implementation and memory model

एक लैम्ब्डा अभिव्यक्ति सिर्फ इतना है कि यह है: एक अभिव्यक्ति। एक बार संकलित हो जाने पर, यह रनटाइम पर एक बंद ऑब्जेक्ट में परिणाम देता है।

5.1.2 लैम्ब्डा भाव [expr.prim.lambda]

एक prvalue अस्थायी (12.2) में एक लैम्ब्डा अभिव्यक्ति परिणाम का मूल्यांकन। इस अस्थायी को बंद वस्तु कहा जाता है।

ऑब्जेक्ट स्वयं कार्यान्वयन-परिभाषित है और संकलक से कंपाइलर में भिन्न हो सकता है।

यहाँ बजना में lambdas के मूल कार्यान्वयन https://github.com/faisalv/clang-glambda

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