2013-12-12 8 views
7

हाल ही में, मैं this post और that post पढ़ रहा हूं जो रिस्टिंग कॉस्ट ऑब्जेक्ट्स को रोकने के लिए सुझाव दे रहा है। यह सुझाव भी जा रहे हैं में his talk में स्टीफ़न टी Lavavej द्वारा दिया जाता है मूल निवासी 2013एक कॉन्स्टर्ड ऑब्जेक्ट को कैसे स्थानांतरित कर रहा है?

मैं एक बहुत ही साधारण परीक्षण लिखा था मुझे समझने जो निर्माता/ऑपरेटर उन सभी मामलों में कहा जाता है मदद करने के लिए:

  • रिटर्निंग कॉन्स या गैर कॉन्स ऑब्जेक्ट्स
  • क्या होगा यदि रिटर्न वैल्यू ऑप्टिमाइज़ेशन (RVO) में शामिल है?
  • क्या होगा यदि नामांकित रिटर्न वैल्यू ऑप्टिमाइज़ेशन (एनआरवीओ) में शामिल हो जाए?

यहाँ परीक्षा है:

#include <iostream> 

void println(const std::string&s){ 
    try{std::cout<<s<<std::endl;} 
    catch(...){}} 

class A{ 
public: 
    int m; 
    A():m(0){println(" Default Constructor");} 
    A(const A&a):m(a.m){println(" Copy Constructor");} 
    A(A&&a):m(a.m){println(" Move Constructor");} 
    const A&operator=(const A&a){m=a.m;println(" Copy Operator");return*this;} 
    const A&operator=(A&&a){m=a.m;println(" Move Operator");return*this;} 
    ~A(){println(" Destructor");} 
}; 

A nrvo(){ 
    A nrvo; 
    nrvo.m=17; 
    return nrvo;} 

const A cnrvo(){ 
    A nrvo; 
    nrvo.m=17; 
    return nrvo;} 

A rvo(){ 
    return A();} 

const A crvo(){ 
    return A();} 

A sum(const A&l,const A&r){ 
    if(l.m==0){return r;} 
    if(r.m==0){return l;} 
    A sum; 
    sum.m=l.m+r.m; 
    return sum;} 

const A csum(const A&l,const A&r){ 
    if(l.m==0){return r;} 
    if(r.m==0){return l;} 
    A sum; 
    sum.m=l.m+r.m; 
    return sum;} 

int main(){ 
    println("build a");A a;a.m=12; 
    println("build b");A b;b.m=5; 
    println("Constructor nrvo");A anrvo=nrvo(); 
    println("Constructor cnrvo");A acnrvo=cnrvo(); 
    println("Constructor rvo");A arvo=rvo(); 
    println("Constructor crvo");A acrvo=crvo(); 
    println("Constructor sum");A asum=sum(a,b); 
    println("Constructor csum");A acsum=csum(a,b); 
    println("Affectation nrvo");a=nrvo(); 
    println("Affectation cnrvo");a=cnrvo(); 
    println("Affectation rvo");a=rvo(); 
    println("Affectation crvo");a=crvo(); 
    println("Affectation sum");a=sum(a,b); 
    println("Affectation csum");a=csum(a,b); 
    println("Done"); 
    return 0; 
} 

और यहाँ (NRVO और RVO के साथ) को रिलीज़ मोड में उत्पादन होता है: :

build a 
    Default Constructor 
build b 
    Default Constructor 
Constructor nrvo 
    Default Constructor 
Constructor cnrvo 
    Default Constructor 
Constructor rvo 
    Default Constructor 
Constructor crvo 
    Default Constructor 
Constructor sum 
    Default Constructor 
    Move Constructor 
    Destructor 
Constructor csum 
    Default Constructor 
    Move Constructor 
    Destructor 
Affectation nrvo 
    Default Constructor 
    Move Operator 
    Destructor 
Affectation cnrvo 
    Default Constructor 
    Copy Operator 
    Destructor 
Affectation rvo 
    Default Constructor 
    Move Operator 
    Destructor 
Affectation crvo 
    Default Constructor 
    Copy Operator 
    Destructor 
Affectation sum 
    Copy Constructor 
    Move Operator 
    Destructor 
Affectation csum 
    Default Constructor 
    Move Constructor 
    Destructor 
    Copy Operator 
    Destructor 
Done 
    Destructor 
    Destructor 
    Destructor 
    Destructor 
    Destructor 
    Destructor 
    Destructor 
    Destructor 

क्या मैं understant नहीं है यह है "कन्स्ट्रक्टर सीएसयूएम" परीक्षण में चलने वाले चालक का उपयोग क्यों किया जाता है?

रिटर्न ऑब्जेक्ट कॉन्स है इसलिए मुझे वास्तव में लगता है कि इसे कॉपी कन्स्ट्रक्टर को कॉल करना चाहिए।

मुझे यहां क्या याद आ रही है?

यह संकलक से एक बग नहीं होना चाहिए, दोनों दृश्य स्टूडियो और क्लैंग एक ही आउटपुट देते हैं।

+0

uint4 क्या है? यह इसके कारण यहां संकलित नहीं होता है, जो मुझे आश्चर्यचकित करता है कि यदि आप यहां मौजूद कोड को संकलित और निष्पादित करते हैं ... – PlasmaHH

+0

क्षमा करें यह सिर्फ एक int है। मैंने सवाल संशोधित किया। – Arnaud

+2

एकाधिक रिटर्न विकल्पों के द्वारा एनआरओओ को ब्लॉक करता है जो समान चर नहीं हैं। – Yakk

उत्तर

4

यह क्या मुझे समझ नहीं आता है: क्यों कदम निर्माता प्रयोग किया जाता है "कन्स्ट्रक्टर सीएसयूएम" परीक्षण में?

इस विशेष मामले में कंपाइलर को [एन] आरवीओ करने की अनुमति है, लेकिन ऐसा नहीं किया। दूसरी सबसे अच्छी बात यह है कि लौटे ऑब्जेक्ट को स्थानांतरित करना है।

रिटर्न ऑब्जेक्ट कॉन्स है इसलिए मुझे वास्तव में लगता है कि इसे कॉपी कन्स्ट्रक्टर को कॉल करना चाहिए।

इससे कोई फर्क नहीं पड़ता। लेकिन मुझे लगता है कि यह पूरी तरह से स्पष्ट नहीं है, इसलिए मूल्य को वापस करने के लिए इसका क्या मतलब है, और [एन] आरवीओ क्या है।

T f() { 
    T obj; 
    return obj; // [1] Alternatively: return T(); 
} 
void g() { 
    f();   // ignore the value 
} 

इस लाइन में चिह्नित के रूप में [1] वहाँ दिए गए मान के लिए स्थानीय/अस्थायी वस्तु से एक प्रति है: उसके लिए, सबसे सरल दृष्टिकोण लौटे वस्तु की अनदेखी करने के लिए है। भले ही मूल्य पूरी तरह से अनदेखा किया गया है। उपर्युक्त कोड में आप यही प्रयोग कर रहे हैं।

आप के रूप में दिए गए मान को अनदेखा नहीं करते हैं,:

T t = f(); 

वहाँ धारणात्मक है t स्थानीय चर करने के लिए दिए गए मान से एक दूसरे की नकल। उस दूसरी प्रति को आपके सभी मामलों में elided किया जा रहा है।

पहली प्रतिलिपि के लिए, क्या ऑब्जेक्ट लौटाया जा रहा है const है या इससे कोई फर्क नहीं पड़ता, संकलक निर्धारित करता है कि [वैचारिक प्रतिलिपि/चाल] कन्स्ट्रक्टर के तर्कों के आधार पर क्या करना है, यह नहीं कि वस्तु का निर्माण किया जा रहा है या नहीं const या नहीं।

// a is convertible to T somehow 
const T ct(a); 
T t(a); 

चाहे गंतव्य वस्तु स्थिरांक है या नहीं कोई फर्क नहीं पड़ता, संकलक तर्क, नहीं गंतव्य के आधार पर सबसे अच्छा निर्माता खोजने के लिए की जरूरत है: यह एक ही रूप है।

अब अगर हम अपने व्यायाम करने के लिए है कि वापस ले, कि प्रतिलिपि निर्माता नहीं बुलाया जाता है सुनिश्चित करने के लिए, आप return बयान के तर्क संशोधित करने की आवश्यकता:

A force_copy(const A&l,const A&r){ // A need not be `const` 
    if(l.m==0){return r;} 
    if(r.m==0){return l;} 
    const A sum; 
    return sum; 
} 

कि प्रतिलिपि निर्माण को गति प्रदान करना चाहिए, लेकिन फिर फिर यह इतना आसान है कि संकलक पूरी तरह से प्रतिलिपि बना सकता है अगर उसे यह उपयुक्त लगे।

1

मैंने जो देखा है, उससे चालक को कॉपी कन्स्ट्रक्टर पर प्राथमिकता मिलती है। जैसा कि यक कहते हैं, आप कई रिटर्न पथों के कारण चालक कन्स्ट्रक्टर को नहीं बढ़ा सकते हैं।

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2002/n1377.htm#Copy%20vs%20Move

rvalues ​​rvalue संदर्भ का चुनाव करेगा। lvalues ​​lvalue संदर्भ पसंद करेंगे। आर/एल-वैल्यू रूपांतरणों के सापेक्ष सीवी योग्यता रूपांतरण माध्यमिक माना जाता है। रावल अभी भी एक कॉस्ट लैवल्यू संदर्भ (कॉन्स ए &) से जुड़ सकते हैं, लेकिन केवल तभी लोड हो सकता है जब अधिभार सेट में आकर्षक रावल्यू संदर्भ नहीं है। lvalues ​​ को एक रावल्यू संदर्भ से जोड़ सकते हैं, लेकिन यह ओवरलोड सेट में मौजूद होने पर एक lvalue संदर्भ पसंद करेंगे। नियम कि एक और सीवी-योग्य ऑब्जेक्ट कम सीवी-योग्य संदर्भ स्टैंड से बंधे नहीं हो सकता है ... दोनों lvalue और रावल संदर्भों के लिए।

इस बिंदु पर एक और भाषा परिष्करण किया जा सकता है।जब एक समारोह से स्वत: भंडारण के साथ एक गैर-सीवी-योग्य वस्तु लौटने, वहाँ rvalue करने के लिए एक अंतर्निहित डाली होना चाहिए:

string 
operator+(const string& x, const string& y) 
{ 
    string result; 
    result.reserve(x.size() + y.size()); 
    result = x; 
    result += y; 
    return result; // as if return static_cast<string&&>(result); 
} 

तर्क का एक स्वत: पदानुक्रम में इस निहित डाली परिणाम से उत्पन्न " ले जाने के अर्थ विज्ञान "सर्वश्रेष्ठ से सबसे खराब करने के लिए:

If you can elide the move/copy, do so (by present language rules) 
Else if there is a move constructor, use it 
Else if there is a copy constructor, use it 
Else the program is ill formed 

तो क्या आप मानकों में const & को दूर तो क्या होगा? यह अभी भी चालक कन्स्ट्रक्टर को कॉल करेगा, लेकिन पैरामीटर के लिए कॉपी कन्स्ट्रक्टर को कॉल करेगा। क्या होगा यदि आप इसके बजाय एक कॉन्स ऑब्जेक्ट वापस करते हैं? यह स्थानीय चर के लिए कॉपी कन्स्ट्रक्टर को कॉल करेगा। यदि आप const & वापस करते हैं तो क्या होगा? यह कॉपी कन्स्ट्रक्टर भी कॉल करेगा।

1

उत्तर है कि आपके A sum स्थानीय चर const A समारोह से वापस लौटे में ले जाया जा रहा है (इस चाल निर्माता उत्पादन होता है) और फिर A acsum में दिए गए मान से प्रतिलिपि संकलक द्वारा elided किया जा रहा है (इसलिए है कोई कॉपी कन्स्ट्रक्टर आउटपुट नहीं)।

1

मैं संकलित बाइनरी, (VC12 रिहाई का निर्माण, O2) disassembled और मेरे निष्कर्ष है:

move आपरेशन एक ढेर-आवंटित const A अस्थायी वस्तु के लिए वापसी से पहले csum(a,b) अंदर परिणाम स्थानांतरित करने के लिए, के रूप में इस्तेमाल किया जा रहा है बाद में A& operator=(const A&) के लिए पैरामीटर।

move आपरेशन नहीं कर सकते move सीवी योग्य चर, लेकिन csum से पहले वापसी, sum चर अभी भी एक गैर स्थिरांक चर रहा है, इसलिए moved हो सकता है; और बाद में उपयोग के बाद moved होने की आवश्यकता है।

const संशोधक बस वापसी के बाद move को संकलक न करे, लेकिन csum अंदर move न करे नहीं है। आप csum से const निकालते हैं, तो परिणाम होगा:

Default Constructor 
Move Constructor 
Destructor 
Move Operator 
Destructor 

Btw, अपने परीक्षण कार्यक्रम एक बग कि a = sum(a, b); गलत, एक के डिफ़ॉल्ट ctor होना चाहिए प्रस्तुत करना होगा:

A() : m(3) { println(" Default Constructor"); } 

या आप a = sum(a, b);

के लिए समझाने के लिए अपने दिए गए उत्पादन मुश्किल मिलेगा

नीचे मैं डीएसयूजी बिल्ड एएसएम का विश्लेषण करने की कोशिश करूंगा। परिणाम एक ही है।

मुख्य (विश्लेषण रिहाई का निर्माण < आत्महत्या> की तरह है _):

a = csum(a, b); 
00F66C95 lea   eax,[b] 
00F66C98 push  eax       ;; param b 
00F66C99 lea   ecx,[a] 
00F66C9C push  ecx       ;; param a 
00F66C9D lea   edx,[ebp-18Ch] 
00F66CA3 push  edx       ;; alloc stack space for return value 
00F66CA4 call  csum (0F610DCh) 
00F66CA9 add   esp,0Ch 
00F66CAC mov   dword ptr [ebp-194h],eax 
00F66CB2 mov   eax,dword ptr [ebp-194h] 
00F66CB8 mov   dword ptr [ebp-198h],eax 
00F66CBE mov   byte ptr [ebp-4],5 
00F66CC2 mov   ecx,dword ptr [ebp-198h] 
00F66CC8 push  ecx 
00F66CC9 lea   ecx,[a] 
00F66CCC call  A::operator= (0F61136h)  ;; assign to var a in main() 
00F66CD1 mov   byte ptr [ebp-4],3 
00F66CD5 lea   ecx,[ebp-18Ch] 
00F66CDB call  A::~A (0F612A8h) 

csum:

if (l.m == 0) { 
00F665AA mov   eax,dword ptr [l] 
00F665AD cmp   dword ptr [eax],0 
00F665B0 jne   csum+79h (0F665D9h) 
    return r; 
00F665B2 mov   eax,dword ptr [r] 
00F665B5 push  eax       ;; r pushed as param for \ 
00F665B6 mov   ecx,dword ptr [ebp+8] 
00F665B9 call  A::A (0F613F2h)    ;; copy ctor of A 
00F665BE mov   dword ptr [ebp-4],0 
00F665C5 mov   ecx,dword ptr [ebp-0E4h] 
00F665CB or   ecx,1 
00F665CE mov   dword ptr [ebp-0E4h],ecx 
00F665D4 mov   eax,dword ptr [ebp+8] 
00F665D7 jmp   csum+0EEh (0F6664Eh) 
    } 
    if (r.m == 0) { 
00F665D9 mov   eax,dword ptr [r] 
00F665DC cmp   dword ptr [eax],0 
00F665DF jne   csum+0A8h (0F66608h) 
    return l; 
00F665E1 mov   eax,dword ptr [l] 
00F665E4 push  eax        ;; l pushed as param for \ 
00F665E5 mov   ecx,dword ptr [ebp+8] 
00F665E8 call  A::A (0F613F2h)     ;; copy ctor of A 
00F665ED mov   dword ptr [ebp-4],0 
00F665F4 mov   ecx,dword ptr [ebp-0E4h] 
00F665FA or   ecx,1 
00F665FD mov   dword ptr [ebp-0E4h],ecx 
00F66603 mov   eax,dword ptr [ebp+8] 
00F66606 jmp   csum+0EEh (0F6664Eh) 
    } 
    A sum; 
00F66608 lea   ecx,[sum] 
    A sum; 
00F6660B call  A::A (0F61244h)     ;; ctor of result sum 
00F66610 mov   dword ptr [ebp-4],1 
    sum.m = l.m + r.m; 
00F66617 mov   eax,dword ptr [l] 
00F6661A mov   ecx,dword ptr [eax] 
00F6661C mov   edx,dword ptr [r] 
00F6661F add   ecx,dword ptr [edx] 
00F66621 mov   dword ptr [sum],ecx 
    return sum; 
00F66624 lea   eax,[sum] 
00F66627 push  eax        ;; sum pushed as param for \ 
00F66628 mov   ecx,dword ptr [ebp+8] 
00F6662B call  A::A (0F610D2h)     ;; move ctor of A (this one is pushed in main as a temp variable on stack) 
00F66630 mov   ecx,dword ptr [ebp-0E4h] 
00F66636 or   ecx,1 
00F66639 mov   dword ptr [ebp-0E4h],ecx 
00F6663F mov   byte ptr [ebp-4],0 
00F66643 lea   ecx,[sum] 
00F66646 call  A::~A (0F612A8h)     ;; dtor of sum 
00F6664B mov   eax,dword ptr [ebp+8] 
} 
संबंधित मुद्दे