Y at - il une classe modèle standard C ++ Library qui fournit des fonctionnalités de concaténation de chaîne efficace, similaire à C # 's StringBuilder ou Java StringBuffer ?
Y at - il une classe modèle standard C ++ Library qui fournit des fonctionnalités de concaténation de chaîne efficace, similaire à C # 's StringBuilder ou Java StringBuffer ?
Réponses:
REMARQUE cette réponse a récemment reçu une certaine attention. Je ne préconise pas cela comme une solution (c'est une solution que j'ai vue dans le passé, avant le TSL). Il est une approche intéressante et ne doit être appliqué sur std::string
ou std::stringstream
si , après votre code de profilage vous découvrez ce fait une amélioration.
J'utilise normalement soit std::string
ou std::stringstream
. Je n'ai jamais eu de problèmes avec ces derniers. Je réserverais normalement de la place d'abord si je connais à l'avance la taille approximative de la corde.
J'ai vu d'autres personnes fabriquer leur propre constructeur de cordes optimisé dans un passé lointain.
class StringBuilder {
private:
std::string main;
std::string scratch;
const std::string::size_type ScratchSize = 1024; // or some other arbitrary number
public:
StringBuilder & append(const std::string & str) {
scratch.append(str);
if (scratch.size() > ScratchSize) {
main.append(scratch);
scratch.resize(0);
}
return *this;
}
const std::string & str() {
if (scratch.size() > 0) {
main.append(scratch);
scratch.resize(0);
}
return main;
}
};
Il utilise deux chaînes, l'une pour la majorité de la chaîne et l'autre comme zone de scratch pour la concaténation de chaînes courtes. Il optimise les ajouts en regroupant les opérations d'ajout courtes dans une petite chaîne, puis en les ajoutant à la chaîne principale, réduisant ainsi le nombre de réallocations requises sur la chaîne principale à mesure qu'elle s'agrandit.
Je n'ai pas eu besoin de cette astuce avec std::string
ou std::stringstream
. Je pense qu'il a été utilisé avec une bibliothèque de chaînes tierce avant std :: string, c'était il y a si longtemps. Si vous adoptez une stratégie comme ce profil, votre candidature en premier.
scratch
corde accomplisse vraiment quoi que ce soit ici. Le nombre de réallocations de la chaîne principale dépendra en grande partie de sa taille finale et non du nombre d'opérations d'ajout, à moins que l' string
implémentation ne soit vraiment médiocre (c'est-à-dire qu'elle n'utilise pas de croissance exponentielle). Donc, «grouper» le append
n'aide pas parce qu'une fois que le sous string
- jacent est grand, il ne se développera qu'occasionnellement de toute façon. En plus de cela, il ajoute un tas d'opérations de copie redondantes et peut plus de réallocations (d'où des appels à new
/ delete
) puisque vous ajoutez à une chaîne courte.
str.reserve(1024);
que ce serait plus rapide que cette chose
La manière C ++ serait d'utiliser std :: stringstream ou simplement des concaténations de chaînes simples. Les chaînes C ++ sont modifiables, donc les considérations de performances de la concaténation sont moins préoccupantes.
en ce qui concerne le formatage, vous pouvez faire tout le même formatage sur un flux, mais d'une manière différente, similaire àcout
. ou vous pouvez utiliser un foncteur fortement typé qui encapsule ceci et fournit une interface de type String.Format, par exemple boost :: format
StringBuilder
existe est de couvrir l'inefficacité du type String de base immuable de Java . En d'autres termes, StringBuilder
c'est du patchwork, nous devrions donc être heureux de ne pas avoir besoin d'une telle classe en C ++.
O(n)
en général.
La std::string.append
fonction n'est pas une bonne option car elle n'accepte pas de nombreuses formes de données. Une alternative plus utile consiste à utiliser std::stringstream
; ainsi:
#include <sstream>
// ...
std::stringstream ss;
//put arbitrary formatted data into the stream
ss << 4.5 << ", " << 4 << " whatever";
//convert the stream buffer into a string
std::string str = ss.str();
Vous pouvez utiliser .append () pour simplement concaténer des chaînes.
std::string s = "string1";
s.append("string2");
Je pense que vous pourriez même être en mesure de faire:
std::string s = "string1";
s += "string2";
En ce qui concerne les opérations de formatage des C # StringBuilder
, je crois snprintf
(ou sprintf
si vous voulez risquer d'écrire du code bogué ;-)) dans un tableau de caractères et de reconvertir en chaîne est à peu près la seule option.
Étant donné que std::string
C ++ est modifiable, vous pouvez l'utiliser. Il a += operator
une append
fonction et une fonction.
Si vous devez ajouter des données numériques, utilisez les std::to_string
fonctions.
Si vous voulez encore plus de flexibilité sous la forme de pouvoir sérialiser n'importe quel objet en une chaîne, utilisez la std::stringstream
classe. Mais vous devrez implémenter vos propres fonctions d'opérateur de streaming pour qu'il fonctionne avec vos propres classes personnalisées.
Un générateur de chaînes pratique pour C ++
Comme beaucoup de gens ont déjà répondu, std :: stringstream est la méthode de choix. Cela fonctionne bien et dispose de nombreuses options de conversion et de formatage. IMO, il a cependant un défaut assez gênant: vous ne pouvez pas l'utiliser comme une seule ligne ou comme une expression. Il faut toujours écrire:
std::stringstream ss;
ss << "my data " << 42;
std::string myString( ss.str() );
ce qui est assez ennuyeux, surtout lorsque vous souhaitez initialiser des chaînes dans le constructeur.
La raison en est que a) std :: stringstream n'a pas d'opérateur de conversion en std :: string et b) les opérateurs << () du stringstream ne renvoient pas une référence stringstream, mais une référence std :: ostream à la place - qui ne peut pas être davantage calculé comme un flux de chaînes.
La solution est de surcharger std :: stringstream et de lui donner de meilleurs opérateurs correspondants:
namespace NsStringBuilder {
template<typename T> class basic_stringstream : public std::basic_stringstream<T>
{
public:
basic_stringstream() {}
operator const std::basic_string<T> () const { return std::basic_stringstream<T>::str(); }
basic_stringstream<T>& operator<< (bool _val) { std::basic_stringstream<T>::operator << (_val); return *this; }
basic_stringstream<T>& operator<< (char _val) { std::basic_stringstream<T>::operator << (_val); return *this; }
basic_stringstream<T>& operator<< (signed char _val) { std::basic_stringstream<T>::operator << (_val); return *this; }
basic_stringstream<T>& operator<< (unsigned char _val) { std::basic_stringstream<T>::operator << (_val); return *this; }
basic_stringstream<T>& operator<< (short _val) { std::basic_stringstream<T>::operator << (_val); return *this; }
basic_stringstream<T>& operator<< (unsigned short _val) { std::basic_stringstream<T>::operator << (_val); return *this; }
basic_stringstream<T>& operator<< (int _val) { std::basic_stringstream<T>::operator << (_val); return *this; }
basic_stringstream<T>& operator<< (unsigned int _val) { std::basic_stringstream<T>::operator << (_val); return *this; }
basic_stringstream<T>& operator<< (long _val) { std::basic_stringstream<T>::operator << (_val); return *this; }
basic_stringstream<T>& operator<< (unsigned long _val) { std::basic_stringstream<T>::operator << (_val); return *this; }
basic_stringstream<T>& operator<< (long long _val) { std::basic_stringstream<T>::operator << (_val); return *this; }
basic_stringstream<T>& operator<< (unsigned long long _val) { std::basic_stringstream<T>::operator << (_val); return *this; }
basic_stringstream<T>& operator<< (float _val) { std::basic_stringstream<T>::operator << (_val); return *this; }
basic_stringstream<T>& operator<< (double _val) { std::basic_stringstream<T>::operator << (_val); return *this; }
basic_stringstream<T>& operator<< (long double _val) { std::basic_stringstream<T>::operator << (_val); return *this; }
basic_stringstream<T>& operator<< (void* _val) { std::basic_stringstream<T>::operator << (_val); return *this; }
basic_stringstream<T>& operator<< (std::streambuf* _val) { std::basic_stringstream<T>::operator << (_val); return *this; }
basic_stringstream<T>& operator<< (std::ostream& (*_val)(std::ostream&)) { std::basic_stringstream<T>::operator << (_val); return *this; }
basic_stringstream<T>& operator<< (std::ios& (*_val)(std::ios&)) { std::basic_stringstream<T>::operator << (_val); return *this; }
basic_stringstream<T>& operator<< (std::ios_base& (*_val)(std::ios_base&)){ std::basic_stringstream<T>::operator << (_val); return *this; }
basic_stringstream<T>& operator<< (const T* _val) { return static_cast<basic_stringstream<T>&>(std::operator << (*this,_val)); }
basic_stringstream<T>& operator<< (const std::basic_string<T>& _val) { return static_cast<basic_stringstream<T>&>(std::operator << (*this,_val.c_str())); }
};
typedef basic_stringstream<char> stringstream;
typedef basic_stringstream<wchar_t> wstringstream;
}
Avec cela, vous pouvez écrire des choses comme
std::string myString( NsStringBuilder::stringstream() << "my data " << 42 )
même dans le constructeur.
Je dois avouer que je n'ai pas mesuré les performances, car je ne l'ai pas encore utilisé dans un environnement qui utilise beaucoup la construction de chaînes, mais je suppose que ce ne sera pas bien pire que std :: stringstream, puisque tout est fait via des références (sauf la conversion en chaîne, mais c'est également une opération de copie dans std :: stringstream)
std::stringstream
ne se comporte pas de cette façon.
Le conteneur Rope peut valoir la peine si vous devez insérer / supprimer une chaîne dans l'emplacement aléatoire de la chaîne de destination ou pour une longue séquence de caractères. Voici un exemple de l'implémentation de SGI:
crope r(1000000, 'x'); // crope is rope<char>. wrope is rope<wchar_t>
// Builds a rope containing a million 'x's.
// Takes much less than a MB, since the
// different pieces are shared.
crope r2 = r + "abc" + r; // concatenation; takes on the order of 100s
// of machine instructions; fast
crope r3 = r2.substr(1000000, 3); // yields "abc"; fast.
crope r4 = r2.substr(1000000, 1000000); // also fast.
reverse(r2.mutable_begin(), r2.mutable_end());
// correct, but slow; may take a
// minute or more.
Je voulais ajouter quelque chose de nouveau pour les raisons suivantes:
Lors d'une première tentative, je n'ai pas réussi à battre
std::ostringstream
de operator<<
efficacité, mais avec plus d'essais j'ai pu faire un StringBuilder plus rapide dans certains cas.
Chaque fois que j'ajoute une chaîne, je stocke simplement une référence quelque part et augmente le compteur de la taille totale.
La vraie façon dont je l'ai finalement implémenté (Horreur!) Est d'utiliser un tampon opaque (std :: vector <char>):
pour l'octet []
pour les chaînes déplacées (chaînes ajoutées std::move
)
std::string
objet (nous avons la propriété)pour les cordes
std::string
objet (pas de propriété)Il y a aussi une petite optimisation, si la dernière chaîne insérée a été déplacée, elle vérifie les octets libres réservés mais inutilisés et y stocke d'autres octets au lieu d'utiliser le tampon opaque (c'est pour économiser de la mémoire, cela le rend en fait légèrement plus lent , dépend peut-être aussi du CPU, et il est rare de voir de toute façon des chaînes avec un espace réservé supplémentaire)
C'était finalement un peu plus rapide que std::ostringstream
mais cela a quelques inconvénients:
ostringstream
conclusion? utilisation
std::ostringstream
Il corrige déjà le plus gros goulot d'étranglement tout en gagnant quelques points de vitesse avec la mise en œuvre de la mine ne vaut pas les inconvénients.
std::ostringstream
.