एक एसटीडी लौटने का कुशल तरीका :: c ++ में वेक्टर


108

कितना डेटा कॉपी किया जाता है, जब किसी std :: वेक्टर को किसी फ़ंक्शन में लौटाया जाता है और यह कितना बड़ा ऑप्टिमाइज़ेशन होगा, तो यह std :: वेक्टर को फ्री-स्टोर (हीप पर) में रखें और इसके बजाय एक पॉइंटर लौटाएँ:

std::vector *f()
{
  std::vector *result = new std::vector();
  /*
    Insert elements into result
  */
  return result;
} 

से अधिक कुशल:

std::vector f()
{
  std::vector result;
  /*
    Insert elements into result
  */
  return result;
} 

?


4
संदर्भ के माध्यम से वेक्टर को कैसे पारित किया जाए और फिर इसे अंदर भरें f?
किरिल किरोव

4
आरवीओ एक सुंदर बुनियादी अनुकूलन है जो अधिकांश संकलक किसी भी क्षण करने में सक्षम होगा।
रेमस रुसानु

जैसे ही उत्तर प्रवाहित होते हैं, यह आपको यह स्पष्ट करने में मदद कर सकता है कि आप C ++ 03 या C ++ 11 का उपयोग कर रहे हैं। दो संस्करणों के बीच सबसे अच्छा अभ्यास काफी अलग है।
ड्रू डॉर्मन


@ किरिल किरोव, क्या मैं इसे फंक्शन की तर्क सूची में डाल सकता हूं। शून्य च (std :: वेक्टर और परिणाम)?
मोर्टन

जवाबों:


141

C ++ 11 में, यह पसंदीदा तरीका है:

std::vector<X> f();

यही है, मूल्य के आधार पर लौटें।

C ++ 11 के साथ, std::vectorचाल-शब्दार्थ है, जिसका अर्थ है कि आपके फ़ंक्शन में घोषित स्थानीय वेक्टर रिटर्न पर ले जाया जाएगा और कुछ मामलों में भी इस कदम को संकलक द्वारा समाप्त किया जा सकता है।


13
@LeonidVolnitsky: हाँ अगर यह स्थानीय है । वास्तव में, return std::move(v);यह कदम के साथ भी संभव हो गया था, तो चाल-elision निष्क्रिय कर देगा return v;। तो बाद वाला पसंद किया जाता है।
नवाज

1
@juanchopanza: मुझे ऐसा नहीं लगता। C ++ 11 से पहले, आप इसके खिलाफ बहस कर सकते थे क्योंकि वेक्टर को स्थानांतरित नहीं किया जाएगा; और RVO एक कंपाइलर-डिपेंडेंट चीज़ है! 80 और 90 के दशक की चीजों के बारे में बात करें।
नवाज

2
वापसी मूल्य के बारे में मेरी समझ (मूल्य से) है: 'ले जाया गया है' के बजाय, कॉल्ली में वापसी मूल्य कॉलर के ढेर पर बनाया गया है, इसलिए केली में सभी ऑपरेशन इन-प्लेस हैं, आरवीओ में स्थानांतरित करने के लिए कुछ भी नहीं है। । क्या वो सही है?
r0ng

2
@ r0ng: हाँ, यह सच है। यह है कि संकलक आमतौर पर आरवीओ को लागू करते हैं।
नवाज

1
@ नवाज़ यह नहीं है। अब कोई चाल भी नहीं है।
को ऑर्बिट में

71

आपको मूल्य से लौटना चाहिए।

मान द्वारा लौटने की दक्षता में सुधार करने के लिए मानक में एक विशिष्ट विशेषता है। इसे "कॉपी एलीशन" कहा जाता है, और विशेष रूप से इस मामले में "नाम वापसी मूल्य अनुकूलन (NRVO)"।

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

जब NRVO लागू किया जाता है, तो निम्नलिखित कोड में कोई प्रतिलिपि नहीं होगी:

std::vector<int> f() {
    std::vector<int> result;
    ... populate the vector ...
    return result;
}

std::vector<int> myvec = f();

लेकिन उपयोगकर्ता ऐसा करना चाह सकता है:

std::vector<int> myvec;
... some time later ...
myvec = f();

कॉपी एलिसेंस यहां कॉपी को रोक नहीं सकता क्योंकि यह इनिशियलाइजेशन के बजाय असाइनमेंट है। हालाँकि, आपको चाहिए अभी भी मूल्य से वापस । सी ++ 11 में, असाइनमेंट को कुछ अलग से अनुकूलित किया जाता है, जिसे "मूव शब्दार्थ" कहा जाता है। C ++ 03 में, उपरोक्त कोड एक कॉपी का कारण बनता है, और हालांकि सिद्धांत में एक अनुकूलक इससे बचने में सक्षम हो सकता है, व्यवहार में यह बहुत मुश्किल है। तो इसके बजाय myvec = f(), C ++ 03 में आपको यह लिखना चाहिए:

std::vector<int> myvec;
... some time later ...
f().swap(myvec);

एक और विकल्प है, जो उपयोगकर्ता को अधिक लचीला इंटरफ़ेस प्रदान करना है:

template <typename OutputIterator> void f(OutputIterator it) {
    ... write elements to the iterator like this ...
    *it++ = 0;
    *it++ = 1;
}

आप उसके बाद मौजूदा वेक्टर-आधारित इंटरफ़ेस का भी समर्थन कर सकते हैं:

std::vector<int> f() {
    std::vector<int> result;
    f(std::back_inserter(result));
    return result;
}

यह आपके मौजूदा कोड की तुलना में कम कुशल हो सकता है, अगर आपका मौजूदा कोड reserve()एक निश्चित राशि से अधिक जटिल तरीके से उपयोग करता है। लेकिन अगर आपका मौजूदा कोड मूल रूप push_backसे वेक्टर पर बार-बार कॉल करता है, तो यह टेम्पलेट-आधारित कोड उतना ही अच्छा होना चाहिए।


वास्तव में सबसे अच्छा और विस्तृत उत्तर दिया। हालाँकि, आपके स्वैप () संस्करण में ( NR ++ के बिना C ++ 03 के लिए ) आपके पास अभी भी एक कॉपी-निर्माता की प्रतिलिपि f () के अंदर बनी होगी: परिवर्तनशील परिणाम से एक छिपी हुई अस्थायी वस्तु पर, जो कि पिछली बार myvec पर होगी
JenyaKh

@JenyaKh: निश्चित रूप से, यह एक गुणवत्ता-कार्यान्वयन का मुद्दा है। मानक की आवश्यकता नहीं थी कि C ++ 03 कार्यान्वयन NRVO को लागू किया, जैसे कि इसे फ़ंक्शन इनलाइनिंग की आवश्यकता नहीं थी। फ़ंक्शन इनलाइनिंग से अंतर यह है कि एनएलवीओ करते समय इनलाइनिंग शब्दार्थ या आपके कार्यक्रम को नहीं बदलता है। पोर्टेबल कोड NRVO के साथ या उसके बिना काम करना चाहिए। एक विशेष कार्यान्वयन (और विशेष संकलक झंडे) के लिए अनुकूलित कोड कार्यान्वयन के स्वयं के प्रलेखन में NRVO के बारे में गारंटी ले सकता है।
स्टीव जेसोप

3

यह समय है जब मैं आरवीओ के बारे में एक उत्तर देता हूं , मुझे भी ...

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


1

एक सामान्य प्री-सी ++ 11 मुहावरा है कि भरी जाने वाली वस्तु का संदर्भ दिया जाए।

फिर वेक्टर की कोई प्रतिलिपि नहीं है।

void f( std::vector & result )
{
  /*
    Insert elements into result
  */
} 

3
यह C ++ 11 में एक मुहावरा नहीं है।
नवाज

1
@ नवाज़ मैं सहमत हूँ। मुझे यकीन नहीं है कि C ++ पर प्रश्नों के बारे में SO पर सबसे अच्छा अभ्यास क्या है, लेकिन विशेष रूप से C ++ 11 नहीं। मुझे संदेह है कि मुझे एक छात्र को C ++ 11 उत्तर देने के लिए इच्छुक होना चाहिए, C ++ 03 का उत्तर उत्पादन कोड में किसी के लिए कमर-गहरी है। क्या आपकी कोई राय है?
ड्रू डॉर्मन

7
दरअसल, C ++ 11 (जो कि 19 महीने पुराना है) की रिहाई के बाद, मैं हर प्रश्न को C ++ 11 प्रश्न मानता हूं, जब तक कि इसे स्पष्ट रूप से C ++ 03 प्रश्न नहीं कहा जाता।
नवाज

1

यदि कंपाइलर नामांकित रिटर्न वैल्यू ऑप्टिमाइजेशन ( http://msdn.microsoft.com/en-us/library/ms364057(v=vs.80).aspx ) का समर्थन करता है , तो आप सीधे वेक्टर प्रदान कर सकते हैं बशर्ते कि कोई है:

  1. अलग-अलग रास्ते अलग-अलग नामित वस्तुओं को वापस करते हैं
  2. EH राज्यों के साथ एकाधिक वापसी पथ (भले ही समान नाम सभी पथों पर लौटाए गए हों)।
  3. लौटी हुई ऑब्जेक्ट को इनलाइन asm ब्लॉक में संदर्भित किया गया है।

NRVO निरर्थक कॉपी कंस्ट्रक्टर और विध्वंसक कॉल को अनुकूलित करता है और इस प्रकार समग्र प्रदर्शन में सुधार करता है।

आपके उदाहरण में कोई वास्तविक अंतर नहीं होना चाहिए।


0
vector<string> getseq(char * db_file)

और यदि आप इसे मुख्य () पर प्रिंट करना चाहते हैं, तो आपको इसे लूप में करना चाहिए।

int main() {
     vector<string> str_vec = getseq(argv[1]);
     for(vector<string>::iterator it = str_vec.begin(); it != str_vec.end(); it++) {
         cout << *it << endl;
     }
}

-2

"वैल्यू बाय रिटर्न" जितना अच्छा हो सकता है, यह उस तरह का कोड है जो किसी को त्रुटि में ले जा सकता है। निम्नलिखित कार्यक्रम पर विचार करें:

    #include <string>
    #include <vector>
    #include <iostream>
    using namespace std;
    static std::vector<std::string> strings;
    std::vector<std::string> vecFunc(void) { return strings; };
    int main(int argc, char * argv[]){
      // set up the vector of strings to hold however
      // many strings the user provides on the command line
      for(int idx=1; (idx<argc); ++idx){
         strings.push_back(argv[idx]);
      }

      // now, iterate the strings and print them using the vector function
      // as accessor
      for(std::vector<std::string>::interator idx=vecFunc().begin(); (idx!=vecFunc().end()); ++idx){
         cout << "Addr: " << idx->c_str() << std::endl;
         cout << "Val:  " << *idx << std::endl;
      }
    return 0;
    };
  • प्रश्न: ऊपर निष्पादित होने पर क्या होगा? एक: एक coredump।
  • प्रश्न: संकलक ने गलती क्यों नहीं पकड़ी? A: क्योंकि प्रोग्राम वाक्यात्मक है, हालाँकि शब्दार्थ नहीं, सही है।
  • प्रश्न: यदि आप संदर्भ को वापस करने के लिए vecFunc () को संशोधित करते हैं तो क्या होता है? ए: कार्यक्रम अपेक्षित परिणाम प्राप्त करने और पूरा करने के लिए चलता है।
  • प्रश्न: क्या अंतर है? A: संकलक को अनाम ऑब्जेक्ट बनाने और प्रबंधित करने की आवश्यकता नहीं है। प्रोग्रामर ने संकलक को निर्देश दिया है कि वह दो अलग-अलग ऑब्जेक्ट्स के बजाय इट्रेटर और एंडपॉइंट निर्धारण के लिए बिल्कुल एक ऑब्जेक्ट का उपयोग करें, जैसा कि टूटा हुआ उदाहरण करता है।

यदि कोई व्यक्ति GNU g ++ रिपोर्टिंग विकल्पों का उपयोग करता है तो भी उपरोक्त त्रुटिपूर्ण कार्यक्रम कोई त्रुटि नहीं दर्शाएगा। -Wextra -Weffff ++

यदि आप एक मूल्य का उत्पादन करते हैं, तो निम्न दो बार vecFunc () को कॉल करने के स्थान पर काम करेगा:

   std::vector<std::string> lclvec(vecFunc());
   for(std::vector<std::string>::iterator idx=lclvec.begin(); (idx!=lclvec.end()); ++idx)...

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


3
यदि उपयोगकर्ता सामान्य रूप से C ++ से परिचित नहीं है, तो यह गलत हो सकता है कि यह एक उदाहरण है। कोई व्यक्ति जो कि .net या जावास्क्रिप्ट जैसी ऑब्जेक्ट आधारित भाषाओं से परिचित है, शायद यह मान लेगा कि स्ट्रिंग वेक्टर को हमेशा एक पॉइंटर के रूप में पास किया जाता है और इसलिए आपके उदाहरण में हमेशा उसी ऑब्जेक्ट को इंगित किया जाएगा। vecfunc ()। start () और vecfunc () end () आवश्यक रूप से आपके उदाहरण में मेल नहीं खाएंगे क्योंकि वे स्ट्रिंग वेक्टर की प्रतियां होनी चाहिए।
मेड्रान

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.