web-dev-qa-db-fra.com

Moyen efficace de retourner un std :: vector en c ++

Combien de données sont copiées, lors du renvoi d'un std :: vector dans une fonction et de la taille d'une optimisation pour placer std :: vector dans free-store (sur le tas) et renvoyer un pointeur à la place

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

plus efficace que:

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

?

34
Morten

En C++ 11, c'est le moyen préféré:

std::vector<X> f();

C'est-à-dire, retour par valeur. 

Avec C++ 11, std::vector possède move-sémantics, ce qui signifie que le vecteur local déclaré dans votre fonction sera déplacé à son retour et que, dans certains cas, même le déplacement peut être supprimé par le compilateur.

55
Nawaz

Vous devriez retourner en valeur.

La norme a une caractéristique spécifique pour améliorer l'efficacité du retour en valeur. Cela s'appelle "copie de décision", et plus précisément dans ce cas "l'optimisation des valeurs de retour nommées (NRVO)".

Les compilateurs n'ont pas à l'implémenter, mais encore une fois, les compilateurs n'ont n'ont pas à implémenter la fonction inline (ou à effectuer une optimisation). Mais les performances des bibliothèques standard peuvent être assez médiocres si les compilateurs ne s’optimisent pas et si tous les compilateurs sérieux implémentent l’inline et les NRVO (et d’autres optimisations).

Lorsque NRVO est appliqué, il n'y aura pas de copie dans le code suivant:

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

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

Mais l'utilisateur peut vouloir faire ceci:

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

Copier élision n'empêche pas une copie ici car il s'agit d'une affectation plutôt que d'une initialisation. Cependant, vous devriez still retourner par valeur. En C++ 11, l'affectation est optimisée par quelque chose de différent, appelé "sémantique de déplacement". En C++ 03, le code ci-dessus provoque une copie et, bien que en théorie, un optimiseur puisse l’éviter, il est en pratique trop difficile. Donc, au lieu de myvec = f(), en C++ 03, vous devriez écrire ceci:

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

Il existe une autre option, qui consiste à offrir une interface plus flexible à l'utilisateur:

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

Vous pouvez ensuite également prendre en charge l’interface vectorielle existante:

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

Ce pourrait être moins efficace que votre code existant, si votre code existant utilise reserve() de manière plus complexe qu'un simple montant initial. Mais si votre code existant appelle essentiellement Push_back sur le vecteur à plusieurs reprises, ce code basé sur un modèle devrait être aussi bon.

31
Steve Jessop

Il est temps que je poste une réponse à propos de RVO , moi aussi ...

Si vous retournez un objet par valeur, le compilateur l'optimise souvent pour qu'il ne soit pas construit deux fois, car il est superflu de le construire temporairement dans la fonction, puis de le copier. C'est ce qu'on appelle l'optimisation de la valeur de retour: l'objet créé sera déplacé au lieu d'être copié.

3
user529758
vector<string> getseq(char * db_file)

Et si vous voulez l’imprimer sur main (), vous devez le faire en boucle.

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;
     }
}
0
Akash Kandpal

Oui, retour par valeur. Le compilateur peut le gérer automatiquement.

0
bruce

Un idiome courant consiste à transmettre une référence à l'objet à remplir.

Ensuite, il n'y a pas de copie du vecteur.

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

Si le compilateur prend en charge Named Return Value Optimization ( http://msdn.Microsoft.com/en-us/library/ms364057(v=vs.80).aspx ), vous pouvez directement renvoyer le vecteur, à condition qu'il n'y ait pas de:

  1. Différents chemins renvoyant différents objets nommés
  2. Plusieurs chemins de retour (même si le même objet nommé est renvoyé sur Tous les chemins) avec les états EH introduits.
  3. L'objet nommé renvoyé est référencé dans un bloc asm en ligne.

NRVO optimise les appels du constructeur et du destructeur de copie redondante et améliore ainsi les performances globales. 

Il ne devrait y avoir aucune différence réelle dans votre exemple.

0
taocp

Aussi beau que soit le "retour par valeur", c'est le genre de code qui peut conduire à une erreur. Considérez le programme suivant:

    #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;
    };
  • Q: Que se passera-t-il quand ce qui précède est exécuté? A: Un coredump. 
  • Q: Pourquoi le compilateur n'a-t-il pas détecté l'erreur? R: Parce que le programme est Syntaxiquement, bien que non sémantiquement, correct.
  • Q: Que se passe-t-il si vous modifiez vecFunc () pour renvoyer une référence? R: Le programme s’achève et produit le résultat attendu.
  • Q: Quelle est la différence? R: Le compilateur n'a pas besoin de créer et de gérer des objets anonymes. Le programmeur a demandé au compilateur d'utiliser exactement un objet pour l'itérateur et pour la détermination du point de terminaison, plutôt que deux objets différents, comme le fait l'exemple cassé.

Le programme erroné ci-dessus n'indiquera aucune erreur, même si vous utilisez les options de rapport GNU g ++ -Wall -Wextra -Weffc ++

Si vous devez produire une valeur, les opérations suivantes fonctionneraient à la place d'appeler deux fois vecFunc ():

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

Ce qui précède ne produit également aucun objet anonyme pendant l'itération de la boucle, mais nécessite une opération de copie (qui, comme certains le notent, pourrait être optimisée dans certaines circonstances. Mais la méthode de référence garantit qu'aucune copie ne sera produite. Croire au compilateur Exécuter RVO ne remplace pas l’essai du code le plus efficace possible. Si vous pouvez vous passer de la nécessité pour le compilateur de faire RVO, vous avez une longueur d’avance. 

0
unclesmrgol dragon
   vector<string> func1() const
   {
      vector<string> parts;
      return vector<string>(parts.begin(),parts.end()) ;
   } 
0
Amruth A