2015-07-24 6 views
7

मैं सी ++ के लिए एक नवागंतुक हूं और मैंने हाल ही में एक स्थानीय चर के संदर्भ को वापस करने में एक समस्या में भाग लिया। मैंने std::string& से std::string पर वापसी मूल्य को बदलकर हल किया। हालांकि, मेरी समझ के लिए यह बहुत अक्षम हो सकता है। निम्नलिखित कोड पर विचार करें:वापसी मूल्य की प्रतिलिपि बनाने से कैसे बचें

string hello() 
{ 
    string result = "hello"; 
    return result; 
} 

int main() 
{ 
    string greeting = hello(); 
} 

मेरी समझ करने के लिए, तो:

  • hello() कहा जाता है।
  • स्थानीय चर result को "hello" का मान असाइन किया गया है।
  • result का मान वैरिएबल greeting में कॉपी किया गया है।

यह शायद बात है कि std::string के लिए बहुत ज्यादा नहीं है, लेकिन यह निश्चित रूप से महंगा हो सकता है अगर आपके पास, उदाहरण के लिए, प्रविष्टियों के सैकड़ों के साथ एक हैश तालिका।

आप लौटाए गए अस्थायी की प्रतिलिपि बनाने से कैसे बचते हैं, और इसके बजाय पॉइंटर की ऑब्जेक्ट को ऑब्जेक्ट में वापस लेते हैं (अनिवार्य रूप से, स्थानीय चर की एक प्रति)?


Sidenote:। मैं heard है कि संकलक कभी कभी प्रदर्शन करेंगे वापसी-मूल्य अनुकूलन प्रतिलिपि निर्माता बुला से बचने के लिए, लेकिन मुझे लगता है कि सबसे अच्छा आपके कोड रन कुशलता से बनाने के लिए संकलक अनुकूलन पर भरोसा करने के लिए नहीं है)

+1

ऑप्टिमाइज़ न करें जब तक कि आपने पुष्टि नहीं की है कि आपका समाधान व्यावसायिक आवश्यकताओं को पूरा नहीं करता है और फिर केवल यह अनुकूलित करता है कि प्रोफाइलर आपको क्या धीमा कर रहा है। कंपाइलर इसे संभालने दें। इसे पढ़ना और बनाए रखना आसान होगा। –

+2

सी ++ में यह लगभग निश्चित रूप से एक चालक कन्स्ट्रक्टर का उपयोग करेगा यदि किसी कारण से आरवीओ विफल रहता है। यदि आप पूरी तरह से सुनिश्चित होना चाहते हैं, तो आउटपुट स्ट्रिंग को संदर्भित करने के बजाय संदर्भ में पास करें। आरवीओ अब मानक का हिस्सा है, हालांकि जब तक आपके पास आधुनिक कंपाइलर है, तो आप आम तौर पर इस पर भरोसा कर सकते हैं। –

+0

@PeteBaughman और जोनाथन पॉटर: मुझे एहसास है कि संकलक इसे अनुकूलित कर सकता है, और इससे मेरे आवेदन में भारी ओवरहेड नहीं हो सकता है, लेकिन यह एक मूलभूत आवश्यकता को समझ में नहीं पा रहा है? –

उत्तर

9

आपके प्रश्न में विवरण काफी सही है। लेकिन यह समझना महत्वपूर्ण है कि यह अमूर्त सी ++ मशीन का व्यवहार है।वास्तव में, सार वापसी व्यवहार का प्रामाणिक वर्णन

  1. result प्रकार std::string की एक अनाम मध्यवर्ती अस्थायी वस्तु में बनाई जाए और भी कम इष्टतम है। यह अस्थायी कार्य की वापसी के बाद बनी रहती है।
  2. उस नामहीन मध्यवर्ती अस्थायी वस्तु को फ़ंक्शन रिटर्न के बाद greeting पर कॉपी किया गया है।

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

ध्यान दें कि NRVO के तहत अपने उदाहरण में result"hello" साथ की आरंभीकरण वास्तव में देता है कि "hello" सीधे greeting शुरू से ही में।

तो आधुनिक सी ++ में सबसे अच्छी सलाह है: इसे छोड़ दें और इससे बचें नहीं। मूल्य से इसे वापस करें।

सबसे पहले, संकलक के RVO/NRVO क्षमताओं सकते हैं (और होगा) नकल को खत्म (और घोषणा के बिंदु पर तत्काल प्रारंभ उपयोग करने के लिए जब भी आप के बजाय काम के बाद डिफ़ॉल्ट प्रारंभ के लिए चयन की। कर सकते हैं पसंद करते हैं)। किसी भी आत्म-सम्मानित कंपाइलर में आरवीओ/एनआरवीओ कुछ अस्पष्ट या माध्यमिक नहीं है। यह कुछ संकलक लेखक सक्रिय रूप से कार्यान्वित करने और लागू करने का प्रयास करते हैं।

दूसरा, आरवीओ/एनआरवीओ किसी भी तरह विफल रहता है या लागू नहीं होने पर, हमेशा सेमेन्टिक्स को फ़ॉलबैक समाधान के रूप में ले जाता है। मूविंग स्वाभाविक रूप से रिटर्न-बाय-वैल्यू संदर्भों में लागू होती है और यह गैर-तुच्छ वस्तुओं के लिए पूरी तरह से उड़ाई गई प्रतिलिपि की तुलना में बहुत कम महंगी होती है। और std::string एक जंगम प्रकार है।

+1

इस उत्तर के भाग की व्याख्या करने के लिए मैं सबसे अधिक सहमत हूं; यह सवाल में sidenote से असहमत है वास्तव में कुछ संकलक अनुकूलन पर भरोसा करना अच्छा है। – kasterma

+0

हाँ हाँ हाँ हाँ दस लाख बार हाँ –

3

तरीके कि प्राप्त करने के लिए बहुत सारे हैं:

1) संदर्भ द्वारा कुछ डेटा लौटें

void SomeFunc(std::string& sResult) 
{ 
    sResult = "Hello world!"; 
} 

2) पुन ऑब्जेक्ट पर पॉइंटर को

CSomeHugeClass* SomeFunc() 
{ 
    CSomeHugeClass* pPtr = new CSomeHugeClass(); 
    //... 
    return(pPtr); 
} 

3) सी ++ 11 ऐसे मामलों में एक चालक कन्स्ट्रक्टर का उपयोग कर सकता है। अतिरिक्त जानकारी के लिए thisthis और this देखें।

4

मैं वाक्य से असहमत हूं "मुझे लगता है कि आपके कोड को कुशलता से चलाने के लिए संकलक अनुकूलन पर भरोसा करना सबसे अच्छा नहीं है।" यह मूल रूप से संकलक का पूरा काम है। आपका काम स्पष्ट, सही और रखरखाव स्रोत कोड लिखना है। मुझे कभी भी ठीक करने के लिए हर प्रदर्शन मुद्दे के लिए, मुझे कुछ सरल, सही और रखरखाव करने की बजाए चतुर होने की कोशिश करने वाले डेवलपर द्वारा किए गए सौ या अधिक मुद्दों को ठीक करना पड़ा।

चलिए कुछ चीजों को देखें जो आप कंपाइलर को "मदद" करने की कोशिश करने के लिए कर सकते हैं और देखें कि वे स्रोत कोड की रखरखाव को कैसे प्रभावित करते हैं।

  • आप संदर्भ

के माध्यम से डेटा वापस कर सकता है उदाहरण के लिए:

void hello(std::string& outString) 

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

void hello(std::string& outString); //<-This one could modify outString 
void out(const std::string& toWrite); //<-This one definitely doesn't. 

. . . 

std::string myString; 
hello(myString); //<-This one maybe mutates myString - hard to tell. 
out(myString); //<-This one certainly doesn't, but it looks identical to the one above 

यहां तक ​​कि हैलो की घोषणा स्पष्ट नहीं है। क्या यह बाहर स्ट्रिंग को संशोधित करता है, या लेखक सिर्फ मैला था और संदर्भ को अर्हता प्राप्त करने के लिए भूल गया था? functional style में लिखा गया कोड पढ़ने और समझना और गलती से तोड़ना कठिन है।

बचें

  • आप के बजाय वस्तु के लिए एक सूचक लौट सकते हैं वस्तु लौटने का।

ऑब्जेक्ट पर पॉइंटर लौटने से यह सुनिश्चित करना मुश्किल हो जाता है कि आपका कोड भी सही है। जब तक आप एक unique_ptr का उपयोग नहीं करते हैं, आपको विश्वास करना होगा कि आपकी विधि का उपयोग करने वाले किसी भी व्यक्ति को पूरी तरह से पूरा किया जाता है और जब वे इसके साथ होते हैं तो पॉइंटर को हटाना सुनिश्चित करता है, लेकिन यह RAII नहीं है। std :: स्ट्रिंग पहले से ही एक char * के लिए RAII wrapper का एक प्रकार है जो एक पॉइंटर लौटने से जुड़े डेटा आजीवन मुद्दों को दूर करता है। एक std :: स्ट्रिंग में पॉइंटर लौटने से केवल उन समस्याओं को फिर से पेश किया जाता है जो std :: string को हल करने के लिए डिज़ाइन किया गया था। एक इंसान पर निर्भर होना और सावधानी से अपने फ़ंक्शन के लिए प्रलेखन को पढ़ना और पता होना चाहिए कि पॉइंटर को कब हटाना है और जब पॉइंटर को हटाना नहीं है तो सकारात्मक परिणाम होने की संभावना नहीं है।

बचें

  • कंस्ट्रक्टर्स स्थानांतरित करने के लिए हमें लाता है।

एक चालक कन्स्ट्रक्टर केवल 'परिणाम' से अपने अंतिम गंतव्य तक बिंदु से डेटा के स्वामित्व को स्थानांतरित कर देगा। इसके बाद, 'परिणाम' ऑब्जेक्ट तक पहुंच अमान्य है लेकिन इससे कोई फर्क नहीं पड़ता - आपकी विधि समाप्त हो गई और 'परिणाम' ऑब्जेक्ट दायरे से बाहर हो गया। कोई प्रतिलिपि नहीं, स्पष्ट अर्थशास्त्र के साथ सूचक के स्वामित्व का हस्तांतरण।

आम तौर पर संकलक आपके लिए चालक कन्स्ट्रक्टर को कॉल करेगा। यदि आप वास्तव में पागल हैं (या विशिष्ट ज्ञान है कि संकलक आपकी मदद नहीं करेगा) तो आप std::move का उपयोग कर सकते हैं।

यदि सभी संभव

पर अंत में आधुनिक compilers अद्भुत हैं इस एक कार्य करें। एक आधुनिक सी ++ कंपाइलर के साथ, 99% समय संकलक कॉपी को खत्म करने के लिए कुछ प्रकार के अनुकूलन करने जा रहा है। अन्य 1% समय शायद यह प्रदर्शन के लिए कोई फर्क नहीं पड़ता है। विशिष्ट परिस्थितियों में संकलक std :: स्ट्रिंग GetString() जैसी विधि को फिर से लिख सकता है; GetString शून्य करने के लिए (std :: string & outVar); खुद ब खुद। कोड अभी भी पढ़ने में आसान है, लेकिन अंतिम असेंबली में आपको संदर्भ द्वारा लौटने के सभी वास्तविक या कल्पना गति लाभ मिलते हैं। प्रदर्शन के लिए पठनीयता और रखरखाव का त्याग न करें जब तक कि आपके पास विशिष्ट ज्ञान न हो कि समाधान आपकी व्यावसायिक आवश्यकताओं को पूरा नहीं करता है।

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