Efficiënte manier om een ​​std::vector in c++ te retourneren

Hoeveel data wordt er gekopieerd bij het retourneren van een std::vector in een functie en hoe groot is de optimalisatie om de std::vector in free-store (op de heap) te plaatsen en retourneer in plaats daarvan een aanwijzer, dwz is:

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

efficiënter dan:

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

?


Antwoord 1, autoriteit 100%

In C++11 heeft dit de voorkeur:

std::vector<X> f();

Dat wil zeggen, rendement op waarde.

Met C++11 heeft std::vector move-semantics, wat betekent dat de lokale vector die in uw functie is gedeclareerd, verplaatst bij terugkeer en in sommige gevallen kan zelfs de zet worden weggelaten door de compiler.


Antwoord 2, autoriteit 46%

Je moet op waarde teruggeven.

De standaard heeft een specifieke functie om de efficiëntie van het retourneren op waarde te verbeteren. Het heet “copy elision”, en meer specifiek in dit geval de “named return value optimization (NRVO)”.

Compilers hoeven het niet te implementeren, maar compilers hoeven niet functie-inlining te implementeren (of helemaal geen optimalisatie uit te voeren). Maar de prestaties van de standaardbibliotheken kunnen behoorlijk slecht zijn als compilers niet optimaliseren, en alle serieuze compilers implementeren inlining en NRVO (en andere optimalisaties).

Als NRVO wordt toegepast, wordt de volgende code niet gekopieerd:

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

Maar de gebruiker zou dit misschien willen doen:

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

Copy elision verhindert een kopie hier niet omdat het een opdracht is in plaats van een initialisatie. U moet echter nog op waarde retourneren. In C++11 wordt de toewijzing geoptimaliseerd door iets anders, genaamd “move semantics”. In C++03 veroorzaakt de bovenstaande code wel een kopie, en hoewel in theorie een optimizer dit zou kunnen vermijden, is het in de praktijk te moeilijk. Dus in plaats van myvec = f(), zou je in C++03 dit moeten schrijven:

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

Er is nog een andere optie, namelijk het aanbieden van een flexibelere interface aan de gebruiker:

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

Je kunt dan ook nog de bestaande vectorgebaseerde interface ondersteunen:

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

Dit kan minder efficiënt zijn dan uw bestaande code, als uw bestaande code reserve() gebruikt op een manier die complexer is dan alleen een vast bedrag vooraf. Maar als uw bestaande code in feite herhaaldelijk push_back op de vector aanroept, dan zou deze op sjablonen gebaseerde code net zo goed moeten zijn.


Antwoord 3, autoriteit 2%

Het wordt tijd dat ik een antwoord plaats over RVO, ik ook…

Als je een object op waarde retourneert, optimaliseert de compiler dit vaak zodat het niet twee keer wordt geconstrueerd, omdat het overbodig is om het als een tijdelijke functie in de functie te construeren en vervolgens te kopiëren. Dit wordt retourwaarde-optimalisatie genoemd: het gemaakte object wordt verplaatst in plaats van gekopieerd.


Antwoord 4

Als de compiler Named Return Value Optimization ondersteunt (http://msdn.microsoft.com/en-us/library/ms364057(v=vs.80).aspx), kunt u de vector rechtstreeks retourneren, op voorwaarde dat er geen is:

  1. Verschillende paden die verschillende benoemde objecten retourneren
  2. Meerdere retourpaden (zelfs als hetzelfde benoemde object wordt geretourneerd op)
    alle paden) met EH-statussen geïntroduceerd.
  3. Het geretourneerde benoemde object wordt verwezen in een inline asm-blok.

NRVO optimaliseert de redundante copy-constructor- en destructor-aanroepen en verbetert zo de algehele prestaties.

Er zou geen echt verschil in uw voorbeeld moeten zijn.


Antwoord 5

Een algemeen pre-C++11 idioom is om een ​​verwijzing door te geven naar het object dat wordt gevuld.

Dan wordt de vector niet gekopieerd.

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

Antwoord 6

vector<string> getseq(char * db_file)

En als u het op main() wilt afdrukken, moet u dit in een lus doen.

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;
     }
}

Antwoord 7

Hoe mooi “return by value” ook mag zijn, het is het soort code dat tot een fout kan leiden. Overweeg het volgende programma:

    #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;
    };
  • V: Wat gebeurt er als het bovenstaande wordt uitgevoerd? A: Een coredump.
  • V: Waarom heeft de compiler de fout niet opgemerkt? A: Omdat het programma is:
    syntactisch, hoewel niet semantisch, correct.
  • V: Wat gebeurt er als je vecFunc() aanpast om een ​​referentie terug te geven? A: Het programma loopt tot het einde en levert het verwachte resultaat op.
  • V: Wat is het verschil? A: De compiler niet
    anonieme objecten moeten maken en beheren. De programmeur heeft de compiler opdracht gegeven om precies één object te gebruiken voor de iterator en voor het bepalen van het eindpunt, in plaats van twee verschillende objecten zoals het gebroken voorbeeld doet.

Het bovenstaande foutieve programma geeft geen fouten aan, zelfs niet als men de GNU g++ rapportage-opties gebruikt -Wall -Wextra -Weffc++

Als u een waarde moet produceren, werkt het volgende in plaats van twee keer vecFunc() aan te roepen:

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

Het bovenstaande produceert ook geen anonieme objecten tijdens het herhalen van de lus, maar vereist een mogelijke kopieerbewerking (die, zoals sommigen opmerken, onder bepaalde omstandigheden kan worden geoptimaliseerd. Maar de referentiemethode garandeert dat er geen kopie wordt geproduceerd. de compiler zal uitvoeren RVO is geen vervanging voor het proberen om de meest efficiënte code te bouwen die u kunt. Als u de noodzaak voor de compiler om RVO te doen kunt betwisten, loopt u voor op het spel.


Antwoord 8

   vector<string> func1() const
   {
      vector<string> parts;
      return vector<string>(parts.begin(),parts.end()) ;
   } 

Dit is nog steeds efficiënt na c++11 en later omdat complier automatisch move gebruikt in plaats van een kopie te maken.

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Other episodes