J'ai entendu quelques personnes exprimer leurs inquiétudes concernant l'opérateur "+" dans std :: string et diverses solutions de contournement pour accélérer la concaténation. Est-ce que certains d'entre eux sont vraiment nécessaires? Si tel est le cas, quel est le meilleur moyen de concaténer des chaînes en C++?
Le travail supplémentaire n'en vaut probablement pas la peine, sauf si vous avez vraiment besoin d'efficacité. Vous obtiendrez probablement une bien meilleure efficacité simplement en utilisant l'opérateur + = à la place.
Maintenant, après cet avertissement, je vais répondre à votre question actuelle ...
L'efficacité de la classe de chaîne STL dépend de l'implémentation de STL que vous utilisez.
Vous pouvez garantir l'efficacité et avoir un meilleur contrôle vous-même en concaténant manuellement via c fonctions intégrées.
Pourquoi l'opérateur + n'est pas efficace:
Jetez un oeil à cette interface:
template <class charT, class traits, class Alloc>
basic_string<charT, traits, Alloc>
operator+(const basic_string<charT, traits, Alloc>& s1,
const basic_string<charT, traits, Alloc>& s2)
Vous pouvez voir qu'un nouvel objet est retourné après chaque +. Cela signifie qu’un nouveau tampon est utilisé à chaque fois. Si vous faites une tonne d'opérations extra +, ce n'est pas efficace.
Pourquoi vous pouvez le rendre plus efficace:
Considérations pour la mise en œuvre:
Structure de données de corde:
Si vous avez besoin de concaténations très rapides, utilisez une structure de données rope .
Réservez votre dernier espace avant, puis utilisez la méthode append avec un tampon. Par exemple, supposons que votre chaîne finale compte 1 million de caractères:
std::string s;
s.reserve(1000000);
while (whatever)
{
s.append(buf,len);
}
Je ne m'inquiéterais pas pour ça. Si vous le faites en boucle, les chaînes vont toujours préallouer de la mémoire pour minimiser les réaffectations - utilisez simplement operator+=
dans ce cas. Et si vous le faites manuellement, quelque chose comme ça ou plus
a + " : " + c
Ensuite, il crée des temporaires - même si le compilateur pourrait éliminer certaines copies de valeurs de retour. En effet, dans un operator+
appelé successivement, il ne sait pas si le paramètre de référence fait référence à un objet nommé ou à un temporaire renvoyé par une invocation sous operator+
. Je préférerais ne pas m'inquiéter avant de ne pas avoir profilé en premier. Mais prenons un exemple pour montrer cela. Nous introduisons d’abord des parenthèses pour clarifier la reliure. Je mets les arguments directement après la déclaration de fonction utilisée pour plus de clarté. Ci-dessous, je montre quelle est l'expression résultante:
((a + " : ") + c)
calls string operator+(string const&, char const*)(a, " : ")
=> (tmp1 + c)
Maintenant, dans cet ajout, tmp1
est ce qui a été renvoyé par le premier appel à operator + avec les arguments montrés. Nous supposons que le compilateur est vraiment intelligent et optimise la copie de la valeur de retour. Nous nous retrouvons donc avec une nouvelle chaîne contenant la concaténation de a
et " : "
. Maintenant, cela se produit:
(tmp1 + c)
calls string operator+(string const&, string const&)(tmp1, c)
=> tmp2 == <end result>
Comparez cela à ce qui suit:
std::string f = "hello";
(f + c)
calls string operator+(string const&, string const&)(f, c)
=> tmp1 == <end result>
Il utilise la même fonction pour une chaîne temporaire et une chaîne nommée! Ainsi, le compilateur doit copier l'argument dans une nouvelle chaîne, puis l'ajouter et le renvoyer à partir du corps de operator+
. Il ne peut pas prendre la mémoire d'un temporaire et ajouter à cela. Plus l'expression est grande, plus le nombre de copies de chaînes à effectuer est important.
Next Visual Studio et GCC prendront en charge la sémantique move de c ++ 1x (complétant la sémantique de la copie ) et les références rvalue en tant qu'addition expérimentale. Cela permet de savoir si le paramètre fait référence à un temporaire ou non. Cela rendra ces ajouts incroyablement rapides, car tout ce qui précède se retrouvera dans un "add-pipeline" sans copies.
Si cela s'avère être un goulot d'étranglement, vous pouvez toujours faire
std::string(a).append(" : ").append(c) ...
Les appels append
ajoutent l'argument à *this
, puis renvoient une référence à eux-mêmes. Donc, aucune copie des temporaires n'est faite ici. Ou alternativement, le operator+=
peut être utilisé, mais vous auriez besoin de parenthèses disgracieuses pour réparer la priorité.
Pour la plupart des applications, ce n'est pas grave. Il vous suffit d'écrire votre code, ignorant parfaitement le fonctionnement exact de l'opérateur +, et de prendre les choses en main si cela devenait un goulet d'étranglement apparent.
Contrairement à .NET System.String, C++ std :: strings est mutable, et peut donc être construit par une simple concaténation aussi rapidement que par d’autres méthodes.
peut-être std :: stringstream à la place?
Mais je suis d’accord avec le sentiment que vous devriez probablement le garder facile à maintenir et compréhensible, puis profilez pour voir si vous rencontrez vraiment des problèmes.
Dans Imperfect C++ , Matthew Wilson présente un concaténateur dynamic string pré-calculant la longueur de la chaîne finale afin de ne disposer que d’une allocation avant de concaténer toutes les parties. Nous pouvons également implémenter un concaténateur statique en jouant avec expression templates .
Ce type d’idée a été implémenté dans l’implémentation std :: string de STLport - cela n’est pas conforme à la norme à cause de ce hack précis.
std::string
operator+
alloue une nouvelle chaîne et copie les deux chaînes d'opérande à chaque fois. Répétez plusieurs fois et cela devient cher, O (n).
std::string
append
et operator+=
d'autre part, augmentez la capacité de 50% chaque fois que la chaîne doit croître. Ce qui réduit considérablement le nombre d’allocations de mémoire et d’opérations de copie, O (log n).
Peu importe les petites chaînes ... Si vous avez de grandes chaînes, vous feriez mieux de les stocker car elles sont en vecteur ou dans une autre collection en tant que parties. Et adaptez votre algorithme pour travailler avec un tel ensemble de données au lieu d'une grosse chaîne.
Je préfère std :: ostringstream pour la concaténation complexe.
Comme pour la plupart des choses, il est plus facile de ne pas faire quelque chose que de le faire.
Si vous souhaitez afficher des chaînes de grande taille sur une interface utilisateur graphique, il se peut que quoi que vous produisiez, vous puissiez mieux gérer les chaînes en morceaux qu'une chaîne de grande taille (par exemple, concaténer du texte dans un éditeur de texte - généralement, les lignes sont séparées). structures).
Si vous souhaitez exporter dans un fichier, diffusez les données en flux plutôt que de créer une chaîne longue et de la sortir.
Je n'ai jamais trouvé nécessaire d'accélérer la concaténation si je supprimais la concaténation inutile du code lent.
Meilleure performance si vous pré-allouez (réservez) de l’espace dans la chaîne résultante.
template<typename... Args>
std::string concat(Args const&... args)
{
size_t len = 0;
for (auto s : {args...}) len += strlen(s);
std::string result;
result.reserve(len); // <--- preallocate result
for (auto s : {args...}) result += s;
return result;
}
Usage:
std::string merged = concat("This ", "is ", "a ", "test!");
Un tableau simple de caractères, encapsulé dans une classe qui garde la trace de la taille du tableau et du nombre d'octets alloués, est le plus rapide.
L'astuce consiste à ne faire qu'une seule grosse allocation au début.
à
https://github.com/pedro-vicente/table-string
Pour Visual Studio 2015, génération de débogage x86, amélioration substantielle par rapport à C++ std :: string.
| API | Seconds
| ----------------------|----|
| SDS | 19 |
| std::string | 11 |
| std::string (reserve) | 9 |
| table_str_t | 1 |