2012-06-06 14 views
5

अगर मैं() एक वर्ग एक (जो मूल्य द्वारा एक वस्तु देता है), और दो कार्यों च() और जी सिर्फ उनकी वापसी चर में अंतर होने:वास्तव में कोई फ़ंक्शन मूल्य से कैसे लौटाता है?

class A 
{ 
    public: 
    A() { cout<<"constructor, "; } 
    A (const A&) { cout<<"copy-constructor, "; } 
    A& operator = (const A&) { cout<<"assignment, "; } 
    ~A() { cout<<"destructor, "; } 
}; 
    const A f(A x) 
    {A y; cout<<"f, "; return y;} 

    const A g(A x) 
    {A y; cout<<"g, "; return x;} 

main() 
{ 
    A a; 
    A b = f(a); 
    A c = g(a); 
} 

अब जब मैं लाइन A b = f(a); निष्पादित, यह आउटपुट करता है:

copy-constructor, constructor, f, destructor, जो यह मानता है कि ऑब्जेक्ट वाई में f() ऑब्जेक्ट वाई में सीधे बनाया गया है अर्थात ऑब्जेक्ट बी की स्मृति स्थान पर, और कोई अस्थायी शामिल नहीं है।

जबकि जब मैं लाइन A c = g(a); निष्पादित, यह आउटपुट:

copy-constructor, constructor, g, copy-constructor, destructor, destructor,

तो सवाल यह है कि क्यों जी() के मामले में वस्तु को सी के स्मृति स्थान पर सीधे बनाया जा सकता है, जिस तरह से यह f() को कॉल करते समय हुआ था? दूसरे मामले में यह एक अतिरिक्त प्रति-निर्माता (जिसे मैं अस्थायी रूप से शामिल करने की वजह से मानता हूं) कहता हूं?

+0

यदि आप संकलक को अनुकूलन करने के लिए चाहते हैं तो आपको ऑप्टिमाइज़ेशन सक्षम करने के साथ संकलित करना होगा। –

+0

मुझे नहीं लगता कि इसमें कंपाइलर अनुकूलन के साथ कुछ भी करना है क्योंकि मैंने पहले से ही कोशिश की है। – cirronimbo

उत्तर

3

अंतर यह है कि g मामले में, आप फ़ंक्शन पर पास किए गए मान को वापस कर रहे हैं। मानक स्पष्ट रूप से बताता है कि किस स्थिति के तहत प्रतिलिपि 12.8 एमपी 1 में elided किया जा सकता है और इसमें एक फंक्शन तर्क से प्रतिलिपि शामिल नहीं है।

असल में समस्या यह है कि तर्क और लौटाई गई वस्तु का स्थान कॉलिंग सम्मेलन द्वारा तय किया जाता है, और संकलक इस तथ्य के आधार पर कॉलिंग सम्मेलन को नहीं बदल सकता है कि कार्यान्वयन (जो शायद यहां दिखाई नहीं दे सकता है कॉल की जगह) तर्क देता है।

मैंने कुछ समय पहले एक छोटा सा ब्लॉग शुरू किया था (मुझे अधिक समय लगता है ...) और मैंने एनआरवीओ के बारे में कुछ लेख लिखे और प्रतिलिपि की प्रतिलिपि बनाई जो इसे स्पष्ट करने में मदद कर सकती है (या नहीं, कौन जानता है :)) :

Value semantics: NRVO

Value semantics: Copy elision

+0

बहुत बहुत धन्यवाद। आपके "[अन] परिभाषित व्यवहार" ने निश्चित रूप से "अच्छी तरह से परिभाषित" तरीके से मेरे कई संदेह हल किए हैं :) लेकिन अगर आप बता सकते हैं कि मुझे आपके लिए कुछ और संदेह हैं तो 1. एनआरवीओ में, जब आप "टाइप एक्स और वाई" से तय करने के लिए "बूल जो" का उपयोग किया जाता है, जो "टाइप" वापस लौटने के लिए होता है, ऐसा लगता है कि मेरा कंपाइलर एलीशन करने में असमर्थ है (मुझे दूसरों पर भी संदेह है।) – cirronimbo

+0

2. मेरे कोड में यदि मैं एक संदर्भ जोड़ता हूं एक अस्थायी अर्थात् "ए और बी = एफ (ए);" तो क्या होता है कि वस्तु का दायरा (माना जाता है कि एफ (ए) द्वारा अस्थायी रूप से लौटाया जाता है) मुख्य अंत तक ब्रेस तक बढ़ता है। तो यह दो चीजों का विरोधाभास कर रहा है- 1. जैसा कि आपने अपने ब्लॉग में उल्लेख किया है कि अस्थायी का पता लेना अवैध है, लेकिन हम इसे यहां कर रहे हैं। 2. इतने लंबे समय तक अस्थायी कैसे रह सकता है? – cirronimbo

+0

3. क्या ऐसा है कि स्थानीय सदस्य के कार्यक्षेत्रों के कार्यों और उसके तर्कों के दायरे अलग-अलग हैं।जैसा कि मैंने कुछ किया "ए ए; ए बी; बी = जी (ए);" फिर लाइन में "बी = जी (ए)" असाइनमेंट के बाद, स्थानीय ऑब्जेक्ट के विनाशक असाइनमेंट ऑपरेटर और तर्क के पहले कहा जाता था। – cirronimbo

7

समस्या यह है कि दूसरे मामले में, आप पैरामीटर में से एक लौट रहे हैं। यह देखते हुए कि आमतौर पर कॉलर की साइट पर पैरामीटर प्रतिलिपि होती है, फ़ंक्शन के भीतर नहीं (इस मामले में main), कंपाइलर प्रतिलिपि बनाता है, और उसके बाद g() में प्रवेश करने के बाद इसे फिर से कॉपी करने के लिए मजबूर किया जाता है।

http://cpp-next.com/archive/2009/08/want-speed-pass-by-value/ से

दूसरा, मैं अभी तक एक संकलक कि प्रति छिपाना होगा क्रमबद्ध के बारे में हमारी कार्यान्वयन में के रूप में जब एक समारोह पैरामीटर दिया जाता है, पता लगाने के लिए है। जब आप सोचते हैं कि इन elisions कैसे किया जाता है, यह समझ में आता है: कुछ प्रकार के अंतर-प्रक्रियात्मक अनुकूलन के बिना, सॉर्ट किए गए कॉलर को यह नहीं पता कि तर्क (और कुछ अन्य वस्तु नहीं) अंततः वापस कर दिया जाएगा, इसलिए संकलक को अवश्य ही तर्क और वापसी मूल्य के लिए ढेर पर अलग जगह आवंटित करें।

class A{ 
public: 
    A(const char* cname) : name(cname){ 
     std::cout << "constructing " << cname << std::endl; 
    } 
    ~A(){ 
     std::cout << "destructing " << name.c_str() << std::endl; 
    } 
    A(A const& a){ 
     if (name.empty()) name = "*tmp copy*"; 
     std::cout 
      << "creating " << name.c_str() 
      << " by copying " << a.name.c_str() << std::endl; 
    } 
    A& operator=(A const& a){ 
     std::cout 
      << "assignment (" 
       << name.c_str() << " = " << a.name.c_str() 
      << ")"<< std::endl; 
     return *this; 
    } 
    std::string name; 
}; 

यहाँ इस वर्ग के उपयोग है::

+0

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

4

यहाँ अपने कोड का एक छोटा संशोधन, आप पूरी तरह से समझने के लिए वहाँ क्या हो रहा है में मदद मिलेगी है

const A f(A x){ 
    std::cout 
     << "// renaming " << x.name.c_str() 
     << " to x in f()" << std::endl; 
    x.name = "x in f()"; 
    A y("y in f()"); 
    return y; 
} 

const A g(A x){ 
    std::cout 
     << "// renaming " << x.name.c_str() 
     << " to x in f()" << std::endl; 
    x.name = "x in g()"; 
    A y("y in g()"); 
    return x; 
} 

int main(){ 
    A a("a in main()"); 
    std::cout << "- - - - - - calling f:" << std::endl; 
    A b = f(a); 
    b.name = "b in main()"; 
    std::cout << "- - - - - - calling g:" << std::endl; 
    A c = g(a); 
    c.name = "c in main()"; 
    std::cout << ">>> leaving the scope:" << std::endl; 
    return 0; 
} 

और यहाँ है आउटपुट के बिना संकलित जब आउटपुट:

constructing a in main() 
- - - - - - calling f: 
creating *tmp copy* by copying a in main() 
// renaming *tmp copy* to x in f() 
constructing y in f() 
creating *tmp copy* by copying y in f() 
destructing y in f() 
destructing x in f() 
- - - - - - calling g: 
creating *tmp copy* by copying a in main() 
// renaming *tmp copy* to x in f() 
constructing y in g() 
creating *tmp copy* by copying x in g() 
destructing y in g() 
destructing x in g() 
>>> leaving the scope: 
destructing c in main() 
destructing b in main() 
destructing a in main() 

आपके द्वारा पोस्ट किया गया आउटपुट Named Return Value Optimization के साथ संकलित प्रोग्राम का आउटपुट है। इस मामले में संकलक अनावश्यक प्रतिलिपि बनाने के लिए प्रयास करता है प्रतिलिपि निर्माता और विनाशक पर कॉल करता है जिसका अर्थ है कि ऑब्जेक्ट लौटने पर, यह ऑब्जेक्ट को अनावश्यक प्रतिलिपि बनाये बिना वापस करने का प्रयास करेगा।

constructing a in main() 
- - - - - - calling f: 
creating *tmp copy* by copying a in main() 
// renaming *tmp copy* to x in f() 
constructing y in f() 
destructing x in f() 
- - - - - - calling g: 
creating *tmp copy* by copying a in main() 
// renaming *tmp copy* to x in f() 
constructing y in g() 
creating *tmp copy* by copying x in g() 
destructing y in g() 
destructing x in g() 
>>> leaving the scope: 
destructing c in main() 
destructing b in main() 
destructing a in main() 

पहले मामले में, *tmp copy*y in f() को कॉपी करके NRVO के बाद से नहीं बना है इसके काम किया है: यहाँ सक्षम NRVO साथ उत्पादन है। दूसरे मामले में हालांकि एनआरवीओ लागू नहीं किया जा सकता है क्योंकि इस समारोह में रिटर्न स्लॉट के लिए एक और उम्मीदवार घोषित कर दिया गया है।अधिक जानकारी के लिए देखें: C++ : Avoiding copy with the "return" statement :)

+0

हाँ, मुझे यह पता है और मैंने इसे अपने कोड में भी किया है यह देखने के लिए कि वास्तव में क्या चल रहा था (हालांकि मैंने कोड की सरलीकृत संस्करण पोस्ट की है जो केवल मेरी समस्या पर जोर देती है)। और यह कोड मुझसे पूछे जाने वाले प्रश्न का कोई उद्देश्य नहीं देता है। जो कुछ भी हो रहा था, उसके लिए कारण था कि क्या हो रहा है। वैसे भी, चिंता दिखाने के लिए धन्यवाद :) – cirronimbo

+0

@cirronimbo: अब मेरा जवाब जांचें, यह बताता है कि एनआरवीओ सक्षम के साथ क्या चल रहा है और यह भी बताता है कि मैंने आपको यह प्रश्न क्यों सुझाया। – LihO

0

यह (लगभग) अनुकूलन कर सकते हैं पूरे जी() फ़ंक्शन कॉल दूर, इस स्थिति में अपने कोड इस तरह दिखता है:

A a; 
A c = a; 

प्रभावी रूप से यह आपका कोड क्या कर रहा है। अब, जैसा कि आप a को बाय-वैल्यू पैरामीटर (यानी संदर्भ नहीं) के रूप में पास करते हैं, तो कंपाइलर को लगभग एक प्रतिलिपि करना पड़ता है, और फिर यह मानदंड इस मान को देता है, इसे दूसरी प्रतिलिपि करना होता है।

एफ() के मामले में, क्योंकि यह प्रभावी रूप से एक अस्थायी रूप से अस्थायी रूप से एक अस्थायी रूप से लौट रहा है, तो संकलक देख सकता है किका उपयोग आंतरिक चर के अंदर आंतरिक चर के भंडारण के रूप में सुरक्षित है।

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