2016-04-01 12 views
25

क्या एक कंपाइलर स्वचालित lvalue-to-rvalue रूपांतरण कर सकता है यदि यह साबित कर सकता है कि लालू का फिर से उपयोग नहीं किया जाएगा? यहाँ स्पष्ट करने के लिए मैं क्या मतलब है एक उदाहरण है:क्या एक अनुकूल कंपेलर std :: move जोड़ सकता है?

void Foo(vector<int> values) { ...} 

void Bar() { 
    vector<int> my_values {1, 2, 3}; 
    Foo(my_values); // may the compiler pretend I used std::move here? 
} 

एक std::move टिप्पणी की लाइन को जोड़ा गया है, तो वेक्टर Foo के पैरामीटर में, ले जाया जा सकता बजाय कॉपी किया गया। हालांकि, जैसा लिखा है, मैंने std::move का उपयोग नहीं किया था।

यह स्पष्ट रूप से साबित करना बहुत आसान है कि टिप्पणी के बाद my_values ​​का उपयोग नहीं किया जाएगा। तो क्या संकलक को वेक्टर को स्थानांतरित करने की अनुमति है, या इसे कॉपी करने की आवश्यकता है?

उत्तर

32

संकलक को के कॉल पर vector से प्रतिलिपि होने की आवश्यकता होती है।

संकलक साबित कर सकते हैं, तो देखते हैं कि कोई नमूदार साइड इफेक्ट के साथ एक मान्य सार मशीन व्यवहार है (सार मशीन व्यवहार के भीतर, एक वास्तविक कंप्यूटर में नहीं है!) कि Foo में std::vector चलती है, तो वहां ऐसा कर सकते हैं।

आपके उपरोक्त मामले में, यह (चलती कोई सार मशीन दिखाई देने वाली दुष्प्रभाव नहीं है) सत्य है; हालांकि, संकलक इसे साबित करने में सक्षम नहीं हो सकता है।

संभवतः नमूदार व्यवहार जब एक std::vector<T> है को कॉपी:

  • तत्वों पर प्रतिलिपि कंस्ट्रक्टर्स लागू। int के साथ ऐसा करने से
  • अलग-अलग समय पर डिफ़ॉल्ट std::allocator<> को आमंत्रित नहीं किया जा सकता है। यह ::new और ::delete (शायद) किसी भी मामले में, ::new और ::delete को उपरोक्त प्रोग्राम में प्रतिस्थापित नहीं किया गया है, इसलिए आप इसे मानक के तहत नहीं देख सकते हैं।
  • T के विनाशक को विभिन्न वस्तुओं पर अधिक बार कॉल करना। int के साथ देखने योग्य नहीं है।
  • vectorFoo पर कॉल के बाद खाली नहीं है। कोई भी इसकी जांच नहीं करता है, इसलिए यह खाली होना-जैसा कि यह नहीं था।
  • बाहरी वेक्टर के तत्वों के संदर्भ या पॉइंटर्स या इटरेटर उन लोगों के मुकाबले अलग हैं। Foo के बाहर वेक्टर के तत्वों में कोई संदर्भ, वैक्टर या पॉइंटर्स नहीं लेते हैं।

तुम कहते हो सकता है "लेकिन क्या हुआ अगर सिस्टम स्मृति से बाहर है कि नमूदार नहीं है, और वेक्टर बड़ी है, है?":

सार मशीन एक नहीं है "स्मृति से बाहर "हालत, गैर-बाधित कारणों के लिए यह कभी-कभी विफल रहता है (std::bad_alloc फेंकता है)। यह असफलता सार मशीन का वैध व्यवहार है, और वास्तविक (वास्तविक) स्मृति (वास्तविक कंप्यूटर पर) आवंटित नहीं होने में विफल नहीं है, तब तक जब स्मृति के अस्तित्व में कोई अवलोकन दुष्प्रभाव नहीं होता है।

एक से थोड़ा अधिक खिलौना मामला:

int main() { 
    int* x = new int[std::size_t(-1)]; 
    delete[] x; 
} 

जबकि इस कार्यक्रम स्पष्ट रूप से जिस तरह से बहुत अधिक स्मृति आबंटित करता है, संकलक कुछ भी आवंटित नहीं करने के लिए स्वतंत्र है।

हम आगे जा सकते हैं। यहां तक ​​कि:

int main() { 
    int* x = new int[std::size_t(-1)]; 
    x[std::size_t(-2)] = 2; 
    std::cout << x[std::size_t(-2)] << '\n'; 
    delete[] x; 
} 

std::cout << 2 << '\n'; में तब्दील किया जा सकता है। वह बड़ा बफर अमूर्त मौजूद होना चाहिए, लेकिन जब तक आपका "असली" प्रोग्राम व्यवहार करता है-सार सारणी मशीन के रूप में, इसे वास्तव में इसे आवंटित नहीं करना पड़ता है।

दुर्भाग्य से, किसी भी उचित पैमाने पर ऐसा करना मुश्किल है। सी ++ प्रोग्राम से बहुत सारे तरीके और जानकारी लीक हो सकती है। तो ऐसे अनुकूलन पर भरोसा करते हैं (भले ही वे होते हैं) अच्छी तरह खत्म नहीं होने जा रहे हैं।


कि इस मुद्दे को भ्रमित कर सकते हैं new के लिए कॉल वालों के बारे में कुछ सामान था, मैं अनिश्चित हूँ अगर यह भले ही एक प्रतिस्थापित ::new था कॉल को छोड़ कानूनी होगा।


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

जब आप return एक स्थानीय चर एक पंक्ति है कि return X; और X तरह लग रहा है में एक समारोह से पहचानकर्ता है, और कहा कि स्थानीय चर (स्टैक पर) स्वत: भंडारण की अवधि का है, आपरेशन परोक्ष एक कदम है, और कंपाइलर (यदि यह कर सकता है) रिटर्न वैल्यू और स्थानीय चर के अस्तित्व को एक ऑब्जेक्ट में बढ़ा सकता है (और move भी छोड़ सकता है)।

एक ही सच है जब आप एक अस्थायी से एक वस्तु का निर्माण है - आपरेशन परोक्ष एक चाल (के रूप में यह एक rvalue के लिए बाध्य किया जाता है) और यह पूरी तरह से कदम दूर छिपाना कर सकते हैं।

इन दोनों मामलों में, कंपाइलर को इसे एक कदम (प्रतिलिपि नहीं) के रूप में पेश करने की आवश्यकता है, और यह चाल को बढ़ा सकता है।

std::vector<int> foo() { 
    std::vector<int> x = {1,2,3,4}; 
    return x; 
} 

कि x कोई std::move है, फिर भी यह वापसी मान में ले जाया जाता है, और उस आपरेशन elided जा सकता है (x और वापसी मान एक वस्तु में तब्दील किया जा सकता है)।

यह:

std::vector<int> foo() { 
    std::vector<int> x = {1,2,3,4}; 
    return std::move(x); 
} 

ब्लॉक इलिजन, करता है इस के रूप में:

std::vector<int> foo(std::vector<int> x) { 
    return x; 
} 

और हम भी इस कदम रोक सकते हैं:

std::vector<int> foo() { 
    std::vector<int> x = {1,2,3,4}; 
    return (std::vector<int> const&)x; 
} 

या यहाँ तक कि:

std::vector<int> foo() { 
    std::vector<int> x = {1,2,3,4}; 
    return 0,x; 
} 

निहित चाल के नियमों के रूप में जानबूझकर नाजुक हैं। (0,x बहुत खराब , ऑपरेटर का उपयोग है)।

अब, निहित-चाल यह पिछले , आधारित एक तरह के मामलों में होने वाली नहीं पर निर्भर की सलाह नहीं दी जाती है: मानक समिति पहले से ही एक अंतर्निहित-स्थानांतरित करने के लिए एक अंतर्निहित कॉपी मामला बदल गया है के बाद से अंतर्निहित-चाल की भाषा में जोड़ा गया था क्योंकि उन्होंने इसे हानिरहित समझा (जहां समारोह को A(B&&) सीटीआर के साथ एक प्रकार देता है, और वापसी विवरण return b; है जहां bB प्रकार है; सी ++ 11 रिलीज पर एक प्रतिलिपि बनाई गई, अब यह एक चाल है।) आगे अंतर्निहित कदम के विस्तार से इनकार नहीं किया जा सकता है: const& पर स्पष्ट रूप से कास्टिंग करना संभवतः भविष्य में इसे रोकने के लिए सबसे विश्वसनीय तरीका है।

+1

क्या आपके पास कोई लिंक है जहां मैं इस बारे में और जान सकता हूं कि यह कैसे काम करता है या सार मशीन कैसे परिभाषित की जाती है? – vu1p3n0x

+2

@ vu1p3n0x सी ++ मानक की एक प्रति पाएं? [यहां एक मसौदा है] (http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3690.pdf)। [यहां एक और है] (http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3376.pdf)। एक सार मशीन के संदर्भ में मानक में सी ++ का व्यवहार मानक में निर्दिष्ट किया गया है। – Yakk

+0

बिल्कुल सही जवाब। –

2

इस मामले में, कंपाइलर my_values से बाहर निकल सकता है। ऐसा इसलिए है क्योंकि देखने योग्य व्यवहार में कोई फर्क नहीं पड़ता है।

नमूदार व्यवहार के सी ++ मानक की परिभाषा का हवाला देते हुए:

एक अनुरूप क्रियान्वयन पर कम से कम आवश्यकताएँ हैं:

  • अस्थिर वस्तुओं तक पहुंच सख्ती से सार मशीन के नियमों के अनुसार मूल्यांकन किया जाता है।
  • प्रोग्राम समाप्ति पर, फाइलों में लिखे गए सभी डेटा संभावित परिणामों में से एक के समान होंगे जो अमूर्त अर्थशास्त्र के अनुसार कार्यक्रम को निष्पादित करते थे।
  • इंटरैक्टिव उपकरणों की इनपुट और आउटपुट गतिशीलता ऐसी फैशन में होगी जो वास्तव में आउटपुट को इनपुट के लिए इंतजार करने से पहले आउटपुट को प्रेरित करती है। एक इंटरैक्टिव डिवाइस का गठन क्या कार्यान्वयन-परिभाषित है।

थोड़ा इस व्याख्या: "फ़ाइलें" यहाँ मानक आउटपुट धारा भी शामिल है, और कहा कि सी ++ मानक द्वारा परिभाषित नहीं कर रहे हैं (तीसरे पक्ष के पुस्तकालयों के लिए कॉल जैसे ऑपरेटिंग सिस्टम कॉल, या) कार्यों की कॉल के लिए, यह यह माना जाना चाहिए कि वे फ़ंक्शंस एक फ़ाइल को लिख सकते हैं, इसलिए इसका एक सारांश यह है कि गैर-मानक फ़ंक्शन कॉल को अवलोकन योग्य व्यवहार भी माना जाना चाहिए।

हालांकि आपका कोड (जैसा कि आपने इसे दिखाया है) में volatile चर नहीं हैं और गैर मानक कार्यों के लिए कोई कॉल नहीं है। तो दो संस्करण (चाल या स्थानांतरित) में समान अवलोकन योग्य व्यवहार होना चाहिए और इसलिए संकलक या तो कर सकता है (या पूरी तरह से फ़ंक्शन को अनुकूलित भी कर सकता है।)

अभ्यास में, निश्चित रूप से, यह आमतौर पर यह साबित करने के लिए इतना आसान नहीं है कि कोई गैर-मानक फ़ंक्शन कॉल नहीं होती है, इसलिए इस तरह के कई अनुकूलन अवसर याद किए जाते हैं। उदाहरण के लिए, इस मामले में संकलक अभी तक नहीं जानता कि डिफ़ॉल्ट ::operator new को आउटपुट उत्पन्न करने वाले फ़ंक्शन के साथ प्रतिस्थापित किया गया है या नहीं।

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