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;
}
?
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.
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.
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é.
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;
}
}
Oui, retour par valeur. Le compilateur peut le gérer automatiquement.
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
*/
}
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:
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.
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;
};
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.
vector<string> func1() const
{
vector<string> parts;
return vector<string>(parts.begin(),parts.end()) ;
}