2012-04-04 11 views
17

this question पर काम करते समय, मैंने देखा कि std::function का जीसीसी (v4.7) का कार्यान्वयन मूल्य के अनुसार लिया जाता है जब उसके तर्क चलता है। निम्नलिखित कोड इस व्यवहार को दिखाता है:क्या `std :: function` को इसके तर्कों को स्थानांतरित करने की अनुमति है?

#include <functional> 
#include <iostream> 

struct CopyableMovable 
{ 
    CopyableMovable()      { std::cout << "default" << '\n'; } 
    CopyableMovable(CopyableMovable const &) { std::cout << "copy" << '\n'; } 
    CopyableMovable(CopyableMovable &&)  { std::cout << "move" << '\n'; } 
}; 

void foo(CopyableMovable cm) 
{ } 

int main() 
{ 
    typedef std::function<void(CopyableMovable)> byValue; 

    byValue fooByValue = foo; 

    CopyableMovable cm; 
    fooByValue(cm); 
} 
// outputs: default copy move move 

हम यहाँ देखते कि cm की एक प्रति किया जाता है (जो उचित लगता है के बाद से byValue के पैरामीटर मान लिया जाता है), लेकिन फिर वहाँ दो कदम हैं। चूंकि functioncm की एक प्रति पर काम कर रहा है, तथ्य यह है कि यह अपने तर्क को स्थानांतरित करता है, इसे एक महत्वपूर्ण कार्यान्वयन विस्तार के रूप में देखा जा सकता है। हालांकि, इस व्यवहार कुछ परेशानी when using function together with bind कारण बनता है:

#include <functional> 
#include <iostream> 

struct MoveTracker 
{ 
    bool hasBeenMovedFrom; 

    MoveTracker() 
     : hasBeenMovedFrom(false) 
    {} 
    MoveTracker(MoveTracker const &) 
     : hasBeenMovedFrom(false) 
    {} 
    MoveTracker(MoveTracker && other) 
     : hasBeenMovedFrom(false) 
    { 
     if (other.hasBeenMovedFrom) 
     { 
      std::cout << "already moved!" << '\n'; 
     } 
     else 
     { 
      other.hasBeenMovedFrom = true; 
     } 
    } 
}; 

void foo(MoveTracker, MoveTracker) {} 

int main() 
{ 
    using namespace std::placeholders; 
    std::function<void(MoveTracker)> func = std::bind(foo, _1, _1); 
    MoveTracker obj; 
    func(obj); // prints "already moved!" 
} 

इस व्यवहार मानक द्वारा अनुमति है? std::function को अपने तर्कों को स्थानांतरित करने की अनुमति है? और यदि हां, तो क्या यह सामान्य है कि हम bind द्वारा std::function में प्रति-मानकों के साथ वापस आवरण रैपर को परिवर्तित कर सकते हैं, भले ही यह प्लेसहोल्डर्स की कई घटनाओं से निपटने के दौरान अप्रत्याशित व्यवहार को ट्रिगर करता है?

+0

ऐसा लगता है कि यह मुद्दा 'std :: function' से प्लेसहोल्डर के साथ अधिक है। अर्थात्, तथ्य यह है कि 'टाई' बनाते समय, मूल तर्क से अपेक्षित आउटपुट दोनों में स्थानांतरित किया जाता है। –

+0

दिलचस्प बात यह है कि विज़ुअल सी ++ 11 कंपाइलर पहले उदाहरण में "डिफ़ॉल्ट कॉपी चाल" प्रिंट करता है और "पहले से स्थानांतरित" प्रिंट नहीं करता है! क्षण में। मुझे आश्चर्य है कि क्या यह अतिरिक्त कदम std :: function और/या सही अग्रेषण के आंतरिक कार्यकलापों से हो सकता है। –

+0

@MatthieuM। क्या आप विस्तारित कर सकते हैं? मैं प्लेसहोल्डर के कार्यान्वयन से बहुत परिचित नहीं हूं। यदि मुद्दा प्लेसहोल्डर्स से आता है, तो 'std :: function' का उपयोग करने के बजाय "बाइंड-रैपर" प्रकार को कम करने के लिए 'auto' का उपयोग करते समय समस्या उत्पन्न नहीं होती है? –

उत्तर

16

std::functionstd::forward के साथ लिपटे फ़ंक्शन में आपूर्ति किए गए तर्कों को पारित करने के लिए निर्दिष्ट किया गया है। जैसे std::function<void(MoveTracker)> के लिए, समारोह कॉल ऑपरेटर

void operator(CopyableMovable a) 
{ 
    f(std::forward<CopyableMovable>(a)); 
} 

के बराबर std::forward<T> के बाद से है जब T नहीं एक संदर्भ प्रकार, यह आपका पहला उदाहरण में चलता रहता है में से एक के लिए खातों है std::move के बराबर है। यह संभव है कि दूसरा std::function के अंदर इंडिकेशन परतों से गुज़रने से आता है।

यह तो भी समस्या आप लिपटे समारोह के रूप में std::bind उपयोग करने के साथ सामना कर रहे हैं के लिए खातों: std::bindभी इसके मापदंडों अग्रेषित करने के लिए निर्दिष्ट है, और इस मामले में यह std::forward कॉल अंदर से उत्पन्न एक rvalue संदर्भ भेजी जा रही है std::function। आपके बाइंड अभिव्यक्ति का फ़ंक्शन कॉल ऑपरेटर इस प्रकार प्रत्येक तर्क के लिए एक रावल्यू संदर्भ अग्रेषित कर रहा है। दुर्भाग्यवश, चूंकि आपने प्लेसहोल्डर का पुन: उपयोग किया है, इसलिए यह दोनों मामलों में एक ही वस्तु के लिए एक रैल्यू संदर्भ है, इसलिए चलने वाले प्रकारों के लिए जो भी पहले बनाया गया है, वह मूल्य को स्थानांतरित करेगा, और दूसरा पैरामीटर खाली शेल प्राप्त करेगा।

+1

ओह, मुझे कभी एहसास नहीं हुआ कि 'std :: forward '' std :: move 'के बराबर था! यह कई चीजों को बताता है। –