2013-07-04 4 views
132

मुझे समझ नहीं नहीं जब मैं std::move का उपयोग करना चाहिए और जब मैं संकलक का अनुकूलन देना चाहिए ... उदाहरण के लिए:सी ++ 11 रिटर्न मूल्य अनुकूलन या स्थानांतरित करें?

using SerialBuffer = vector< unsigned char >; 

// let compiler optimize it 
SerialBuffer read(size_t size) const 
{ 
    SerialBuffer buffer(size); 
    read(begin(buffer), end(buffer)); 
    // Return Value Optimization 
    return buffer; 
} 

// explicit move 
SerialBuffer read(size_t size) const 
{ 
    SerialBuffer buffer(size); 
    read(begin(buffer), end(buffer)); 
    return move(buffer); 
} 

कौन सा उपयोग करना चाहिए?

+0

जो मैंने अभी तक पढ़ा है, उससे सामान्य सहमति आम तौर पर 'चाल' के बजाय आरवीओ का उपयोग करके संकलक पर निर्भर करती है: आधुनिक कंपेलर आरवीओ का उपयोग करने के लिए काफी स्मार्ट हैं और यह 'चाल' से अधिक कुशल है। लेकिन यह सिर्फ "सुनवाई" है, आपको दिमाग है, इसलिए मुझे एक दस्तावेजी स्पष्टीकरण में काफी दिलचस्पी है। – syam

+7

आपको स्थानीय परिवर्तनीय फ़ंक्शन रिटर्न मान के लिए कभी भी स्पष्ट स्थान की आवश्यकता नहीं है। यह निहित कदम है। –

+2

संकलक तब चुनने के लिए स्वतंत्र है: यदि यह संभव है, तो यह आरवीओ का उपयोग करेगा और यदि नहीं, तो यह अभी भी एक कदम उठा सकता है (और यदि प्रकार के लिए कोई कदम संभव नहीं है, तो यह एक प्रतिलिपि करेगा)। –

उत्तर

75

उपयोग विशेष रूप से पहली विधि:

Foo f() 
{ 
    Foo result; 
    mangle(result); 
    return result; 
} 

यह पहले से ही चाल निर्माता का उपयोग करते हैं, यदि उपलब्ध है की अनुमति देगा। वास्तव में, एक स्थानीय चर return कथन में एक रैल्यू संदर्भ से जुड़ा हो सकता है जब प्रतिलिपि की अनुमति दी जाती है।

आपका दूसरा संस्करण सक्रिय रूप से कॉपी एलिशन को प्रतिबंधित करता है। पहला संस्करण सार्वभौमिक रूप से बेहतर है।

97

सभी वापसी मान या तो पहले से ही moved या अनुकूलित किए गए हैं, इसलिए वापसी मूल्यों के साथ स्पष्ट रूप से स्थानांतरित करने की आवश्यकता नहीं है।

कंपाइलर्स को स्वचालित रूप से वापसी मूल्य (प्रतिलिपि को अनुकूलित करने के लिए) को स्थानांतरित करने की अनुमति है, और यहां तक ​​कि चाल को अनुकूलित भी करें!

n3337 मानक प्रारूप (सी ++ 11) की धारा 12.8:

जब कुछ मानदंडों को पूरा किया जाता है, एक कार्यान्वयन छोड़ एक वर्ग वस्तु की कॉपी/कदम निर्माण की अनुमति दी है, भले ही ऑब्जेक्ट के लिए प्रतिलिपि/चाल कन्स्ट्रक्टर और/या विनाशक के दुष्प्रभाव होते हैं। ऐसे मामलों में, कार्यान्वयन के स्रोत और लक्ष्य का उल्लंघन करता है, उसी वस्तु पर को संदर्भित करने के दो अलग-अलग तरीकों के रूप में छोड़ दिया गया है, और उस ऑब्जेक्ट का विनाश पर उस समय के बाद होता है जब दोनों वस्तुओं कॉपी/कदम आपरेशन के optimization.This इलिजन बिना नष्ट कर दिया गया होता, बुलाया प्रतिलिपि इलिजन, निम्नलिखित परिस्थितियों (जो कई प्रतियां खत्म करने के लिए जोड़ा जा सकता है) में की अनुमति दी है:

[.. ।]

उदाहरण: स्थानीय स्वत: वस्तु t की नकल समारोह की वापसी मूल्य के लिए अस्थायी वस्तु में:

class Thing { 
public: 
Thing(); 
    ~Thing(); 
    Thing(const Thing&); 
}; 

Thing f() { 
    Thing t; 
    return t; 
} 

Thing t2 = f(); 

यहाँ इलिजन के लिए मापदंड वर्ग Thing की प्रतिलिपि निर्माता करने के लिए दो कॉल समाप्त करने के लिए जोड़ा जा सकता है f() और ऑब्जेक्ट t2 में उस अस्थायी वस्तु की प्रतिलिपि बनाना। प्रभावी रूप से, स्थानीय ऑब्जेक्ट t का निर्माण सीधे वैश्विक ऑब्जेक्ट t2 को प्रारंभ करने के रूप में देखा जा सकता है, और उस ऑब्जेक्ट का विनाश प्रोग्राम बाहर निकलने पर होगा। Thing पर एक चालक कन्स्ट्रक्टर जोड़ना एक ही प्रभाव है, लेकिन यह अस्थायी ऑब्जेक्ट से t2 पर चलने वाला निर्माण है जो कि elided है। - अंत उदाहरण]

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

+9

मैं विशेष रूप से पूरे "कंपाइलर एक्स कर सकते हैं" तर्क का शौक नहीं हूं। प्रश्न को किसी भी कंपाइलर को सहारा की आवश्यकता नहीं है। यह पूरी तरह से भाषा के बारे में है। और "एक चाल" होने के बारे में "वैकल्पिक" या अस्पष्ट कुछ भी नहीं है। भाषा पूरी तरह से स्पष्ट है कि किस प्रकार के कन्स्ट्रक्टर पैरामीटर रिटर्न वैल्यू से जुड़ सकते हैं (जो एक xvalue है); ओवरलोड रिज़ॉल्यूशन बाकी करता है। –

+4

यह नहीं है कि कंपेलर क्या कर सकते हैं, यह प्रमुख कंपाइलर्स * करते हैं * करते हैं। चीजों को स्पष्ट रूप से चलाना कंपिलरों के कामों के रास्ते में आगे बढ़ने से भी बेहतर हो सकता है। कोई संकलक जो आपको स्पष्ट रूप से आगे बढ़ने की अनुमति देने के लिए पर्याप्त उन्नत है, वह निश्चित रूप से वापसी मूल्यों को स्वचालित रूप से स्थानांतरित करने के लिए पर्याप्त रूप से उन्नत है - क्योंकि अन्य स्थितियों के विपरीत जहां आप स्पष्ट रूप से स्थानांतरित करना चाहते हैं, कंपेलरों को एक अच्छी जगह के रूप में पहचानने के लिए वापसी मूल्य बहुत आसान है ऑप्टिमाइज़ करने के लिए (क्योंकि कोई भी रिटर्न गारंटी है कि वापसी का काम करने वाले फ़ंक्शन में मूल्य का आगे उपयोग नहीं किया जाएगा)। –

+0

@KerrekSB, "... भाषा पूरी तरह से स्पष्ट है कि किस तरह के कन्स्ट्रक्टर पैरामीटर ..." यह मानता है कि एक निर्माता को बुलाया जाना है, और हम सिर्फ चर्चा कर रहे हैं कि किस निर्माण को बुलाया जाएगा। लेकिन आरवीओ के मामले में, वापसी समय पर बुलाए जाने वाले किसी भी कन्स्ट्रक्टर की आवश्यकता नहीं है। मुझे लगता है कि यह "वैकल्पिक" मुद्दा है। –

20

यदि आप स्थानीय चर लौट रहे हैं, तो move() का उपयोग न करें। यह कंपाइलर को एनआरवीओ का उपयोग करने की अनुमति देगा, और उसमें असफल होने पर, कंपाइलर को अभी भी एक चाल करने की अनुमति दी जाएगी (स्थानीय चर return कथन के भीतर आर-मान बन जाते हैं)। उस संदर्भ में move() का उपयोग करने से एनआरवीओ को बाधित किया जाएगा और संकलक को एक चाल का उपयोग करने के लिए मजबूर किया जाएगा (या यदि कोई स्थान अनुपलब्ध है तो एक प्रतिलिपि)। यदि आप स्थानीय चर के अलावा कुछ और लौट रहे हैं, तो एनआरवीओ वैसे भी एक विकल्प नहीं है और आपको move() का उपयोग करना चाहिए यदि (और केवल अगर) आप वस्तु को पुल करने का इरादा रखते हैं।

+0

क्या यह सही है? यदि मैं उदाहरण का पुन: उपयोग करता हूं: http: // en।cppreference.com/w/cpp/language/copy_elision रिटर्न स्टेटमेंट पर std :: move (line 17) जोड़ना, कॉपी elision को अक्षम नहीं करता है। मानक वास्तव में कहता है कि कॉपी एलिजन "std :: move" को छोड़ देगा और रचनाकारों की प्रतिलिपि बनाएगा। –

+0

@ थॉमस लीग्रीस, मुझे आपकी टिप्पणी समझ में नहीं आ रही है। यदि आप 'वापसी v; 'के बारे में बात कर रहे हैं, तो इस रूप में, एनआरवीओ कदम (और प्रतिलिपि) को बढ़ाएगा। सी ++ 14 के तहत, इसे चाल-निष्पादन करने की आवश्यकता नहीं थी, लेकिन प्रतिलिपि बनाने के लिए आवश्यक था (केवल-चाल प्रकारों का समर्थन करने के लिए आवश्यक)। मैं हाल ही में सी ++ मानकों में विश्वास करता हूं, इसे भी आगे बढ़ाना आवश्यक है (अबाध प्रकारों का समर्थन करने के लिए)। यदि लाइन बदले में 'std :: move (v); ', तो आप स्थानीय चर वापस नहीं कर रहे हैं; आप एक अभिव्यक्ति लौट रहे हैं, और एनआरवीओ योग्य नहीं है --- एक चाल (या प्रतिलिपि) की आवश्यकता होगी। –

+0

ऐसा लगता है कि कंपाइलर्स 'std :: move' को हटाने और एनआरवीओ लागू करने के लिए पर्याप्त स्मार्ट हैं। 'वापसी std :: move (v);' _line 17_ पर 'अनुभवजन्य रूप से दिखाता है कि न तो कन्स्ट्रक्टर और न ही प्रति-कन्स्ट्रक्टर को कभी भी बुलाया जाता है (आप "इसे चलाने" पर क्लिक करके और कंपाइलर विकल्प का चयन करके "gcc 4.7 C + +11 ")। हालांकि, क्लैंग एक चेतावनी आउटपुट करता है लेकिन अभी भी एनआरवीओ लागू करने में सक्षम है। तो मुझे लगता है कि 'std :: move' जोड़ने के लिए यह बहुत अच्छा अभ्यास है, लेकिन यह जोड़ना अनिवार्य रूप से एनआरवीओ को रोक नहीं देगा, यह मेरा मुद्दा था। –

25

यह काफी आसान है।

return buffer;

आप ऐसा करते हैं, तो या तो NRVO क्या होगा या यह नहीं होगा। यदि ऐसा नहीं होता है तो buffer से स्थानांतरित हो जाएगा।

return std::move(buffer);

आप ऐसा करते हैं, तो NVRO ऐसा नहीं जाएगा, और buffer से ले जाया जाएगा।

तो यहां std::move का उपयोग करके लाभ उठाने के लिए कुछ भी नहीं है, और बहुत कुछ खोना है।

Buffer read(Buffer&& buffer) { 
    //... 
    return std::move(buffer); 
} 

तो buffer एक rvalue संदर्भ है, तो आप std::move उपयोग करना चाहिए:

इस नियम के लिए एक अपवाद नहीं है। ऐसा इसलिए है क्योंकि संदर्भ एनआरवीओ के लिए योग्य नहीं हैं, इसलिए std::move के बिना इसके परिणामस्वरूप एक प्रतिभा से एक प्रतिलिपि होगी।

यह सिर्फ नियम "हमेशा move rvalue संदर्भ और forward सार्वभौमिक संदर्भ" है, जो शासन पर पूर्वता लेता है "कभी नहीं move एक वापसी मान" का एक उदाहरण है।

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