web-dev-qa-db-fra.com

C++ std :: string append vs Push_back ()

C'est vraiment une question pour mon propre intérêt que je n'ai pas pu déterminer grâce à la documentation. 

Je vois sur http://www.cplusplus.com/reference/string/string/ que cet appendice est complexe:

"Non spécifié, mais généralement linéaire jusqu'à la nouvelle longueur de chaîne."

tandis que Push_back () a une complexité:

"Non spécifié; constante généralement amortie, mais linéaire jusqu'à la nouvelle longueur de chaîne."

Comme exemple de jouet, supposons que je veuille ajouter les caractères "foo" à une chaîne. Aurait

myString.Push_back('f');
myString.Push_back('o');
myString.Push_back('o');

et 

myString.append("foo");

équivaut exactement à la même chose? Ou y a-t-il une différence? Vous pourriez penser que append serait plus efficace, car le compilateur sait combien de mémoire est nécessaire pour étendre la chaîne du nombre de caractères spécifié, alors que Push_back peut avoir besoin de sécuriser la mémoire à chaque appel?

19
Memento Mori

En C++ 03 (pour lequel la plupart de la documentation de "cplusplus.com" est écrite), les complexités n'étaient pas spécifiées car les développeurs de bibliothèques étaient autorisés à effectuer des représentations internes de type copie sur écriture ou "de type corde" pour les chaînes. Par exemple, une implémentation COW peut nécessiter la copie de la chaîne entière si un caractère est modifié et qu'un partage est en cours.

En C++ 11, les implémentations COW et corde sont interdites. Vous devez vous attendre à un temps amorti constant par caractère ajouté ou un temps amorti linéaire dans le nombre de caractères ajoutés pour ajouter une chaîne à la fin. Les implémenteurs peuvent toujours faire des choses relativement folles avec des chaînes (par rapport à, par exemple, std::vector), mais la plupart des implémentations vont se limiter à des choses comme "l'optimisation des petites chaînes".

En comparant Push_back et append, Push_back prive l'implémentation sous-jacente d'informations de longueur potentiellement utiles qu'elle pourrait utiliser pour préallouer de l'espace. De plus, append requiert qu'une implémentation répète deux fois l'entrée pour trouver cette longueur. Le gain ou la perte de performance dépend donc de nombreux facteurs inconnus, tels que la longueur de la chaîne, avant de tenter l'ajout. . Cela dit, la différence est probablement extrêmement extrême. Allez avec append pour cela - c'est beaucoup plus lisible.

32
Billy ONeal

Ajoutez un autre avis ici.

Personnellement, j'estime qu'il est préférable d'utiliser Push_back() lors de l'ajout d'un à un de caractères d'une autre chaîne. Par exemple:

string FilterAlpha(const string& s) {
  string new_s;
  for (auto& it: s) {
    if (isalpha(it)) new_s.Push_back(it);
  }
  return new_s;
}

Si vous utilisez append()here, je remplacerais Push_back(it) par append(1,it), ce qui ne me semble pas lisible.

3
Pei

J'ai eu le même doute, alors j'ai fait un petit test pour vérifier cela (g ++ 4.8.5 avec le profil C++ 11 sous Linux, Intel, 64 bits sous VmWare Fusion).

Et le résultat est intéressant:

 Appuyez: 19 
 Annexez: 21 
 ++++: 34 

Cela pourrait être possible en raison de la longueur de la chaîne (grande), mais l'opérateur + est très coûteux par rapport au Push_back et à l'ajout.

De plus, il est intéressant de noter que lorsque l'opérateur reçoit uniquement un caractère (et non une chaîne), il se comporte de manière très similaire au Push_back. 

Pour ne pas dépendre de variables pré-allouées, chaque cycle est défini dans une portée différente.

Remarque: vCounter utilise simplement gettimeofday pour comparer les différences.

TimeCounter vCounter;

{
    string vTest;

    vCounter.start();
    for (int vIdx=0;vIdx<1000000;vIdx++) {
        vTest.Push_back('a');
        vTest.Push_back('b');
        vTest.Push_back('c');
    }
    vCounter.stop();
    cout << "Push :" << vCounter.elapsed() << endl;
}

{
    string vTest;

    vCounter.start();
    for (int vIdx=0;vIdx<1000000;vIdx++) {
        vTest.append("abc");
    }
    vCounter.stop();
    cout << "append :" << vCounter.elapsed() << endl;
}

{
    string vTest;

    vCounter.start();
    for (int vIdx=0;vIdx<1000000;vIdx++) {
        vTest += 'a';
        vTest += 'b';
        vTest += 'c';
    }
    vCounter.stop();
    cout << "++++ :" << vCounter.elapsed() << endl;
}
3
user2583872

Oui, je m'attendrais également à ce que append() fonctionne mieux pour les raisons que vous avez indiquées et dans une situation où vous devez ajouter une chaîne, il est certainement préférable d'utiliser append() (ou operator+=) (notamment parce que le code est beaucoup plus lisible).

Mais ce que la norme précise, c'est la complexité de l'opération. Et cela est généralement linéaire même pour append(), car en fin de compte chaque caractère de la chaîne ajoutée (et éventuellement tous les caractères en cas de réallocation) doit être copié (cela est vrai même si memcpy ou similaire est utilisé).

0
jogojapan