web-dev-qa-db-fra.com

Équivalent C ++ de StringBuffer / StringBuilder?

Existe-t-il une classe de bibliothèque de modèles standard C++ offrant une fonctionnalité de concaténation de chaînes efficace, similaire à celle de C # StringBuilder ou à celle de Java StringBuffer ?

158
An̲̳̳drew

NOTE Cette réponse a récemment retenu l'attention. Je ne préconise pas cela comme une solution (c'est une solution que j'ai vue dans le passé, avant le TSL). C'est une approche intéressante et ne devrait être appliquée que sur _std::string_ ou _std::stringstream_ si après avoir profilé votre code, vous découvrez que cela apporte une amélioration.

J'utilise normalement soit std::string ou std::stringstream . Je n'ai jamais eu de problèmes avec ceux-ci. Je réserverais normalement d’abord de la place si je connaissais à l’avance la taille approximative de la chaîne.

J'ai vu d'autres personnes créer 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 travail pour concaténer des chaînes courtes. Il optimise les ajouts en mettant en lot les petites opérations d’ajout dans une petite chaîne, puis en les ajoutant à la chaîne principale, ce qui réduit le nombre de réaffectations nécessaires 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 très longtemps. Si vous adoptez une stratégie comme celle-ci, profilez d'abord votre application.

49
iain

La méthode C++ consisterait à utiliser std :: stringstream ou simplement des concaténations de chaînes simples. Les chaînes C++ étant modifiables, les considérations de performance de la concaténation sont moins préoccupantes.

en ce qui concerne le formatage, vous pouvez faire tous 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 ex. boost :: format

138
jk.

La fonction std :: string.append n'est pas une bonne option car elle n'accepte pas beaucoup de formes de données. Une alternative plus utile consiste à utiliser std: stringstream, comme suit:

#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();
79
Stu

std::stringis l'équivalent C++: il est mutable.

39
dan04

Vous pouvez utiliser .append () pour concaténer simplement des chaînes.

std::string s = "string1";
s.append("string2");

Je pense que vous pourriez même être capable de faire:

std::string s = "string1";
s += "string2";

En ce qui concerne les opérations de formatage de StringBuilder de C #, je pense que snprintf (ou sprintf si vous voulez risquer d'écrire du code buggy ;-)) dans un tableau de caractères et le reconvertir en chaîne est à propos de la seule option.

10
Andy Shellam

Puisque std::string en C++ est modifiable, vous pouvez l'utiliser. Il a un += operator et une append fonction.

Si vous devez ajouter des données numériques, utilisez les fonctions std::to_string.

Si vous voulez encore plus de flexibilité pour pouvoir sérialiser n'importe quel objet en chaîne, utilisez la classe std::stringstream. Mais vous devrez implémenter vos propres fonctions d'opérateur de diffusion en continu pour que celui-ci fonctionne avec vos propres classes personnalisées.

5
Daemin

std :: string's + = ne fonctionne pas avec const char * (ce qui ressemble à "string to add" semble être), donc utiliser stringstream est certainement le plus proche de ce qui est requis - vous utilisez simplement << au lieu de +

3
sergeys

n constructeur de chaînes pratique pour c ++

Comme beaucoup de personnes ayant déjà répondu, std :: stringstream est la méthode de choix. Cela fonctionne bien et a beaucoup d'options de conversion et de formatage. L'OMI a cependant un inconvénient assez gênant: vous ne pouvez pas l'utiliser comme un interligne ou comme une expression. Vous devez toujours écrire:

std::stringstream ss;
ss << "my data " << 42;
std::string myString( ss.str() );

ce qui est assez gênant, 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 vers std :: string et b) l'opérateur << () du stringstream ne renvoie pas de référence stringstream, mais une référence std :: ostream - qui ne peut plus être calculé comme un flux de chaîne.

La solution consiste à remplacer std :: stringstream et à 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é la performance, car je ne l'ai pas encore utilisé dans un environnement qui utilise encore beaucoup la construction de chaînes, mais je suppose que ce ne sera pas pire que std :: stringstream, car tout est fait via des références (sauf la conversion en chaîne, mais c'est aussi une opération de copie dans std :: stringstream)

2
user2328447

Le conteneur Rope peut être utile si vous devez insérer/supprimer une chaîne dans la chaîne de place aléatoire de destination ou pour une longue séquence de caractères. Voici un exemple de la mise en œuvre 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.
1
Igor

Je voulais ajouter quelque chose de nouveau pour les raisons suivantes:

À un premier essai je n'ai pas réussi à battre

std::ostringstream's operator<<

efficacité, mais avec plus d’essais, j’ai pu créer 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 façon dont je l’ai finalement implémenté (Horror!) Consiste à utiliser un tampon opaque (std :: vector <char>):

  • En-tête de 1 octet (2 bits pour indiquer si les données suivantes sont: chaîne déplacée, chaîne ou octet [])
  • 6 bits pour dire la longueur en octets []

pour byte []

  • Je stocke directement des octets de chaînes courtes (pour un accès séquentiel à la mémoire)

pour les chaînes déplacées (chaînes ajoutées avec std::move)

  • Le pointeur sur un objet std::string (nous en avons la propriété)
  • place un drapeau dans la classe s'il y a des octets réservés non utilisés

pour les chaînes

  • Le pointeur sur un objet std::string (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 la présence d'octets libres réservés mais non utilisés et stocke d'autres octets au lieu d'utiliser le tampon opaque (ceci permet d'économiser de la mémoire, elle le ralentit en fait légèrement , dépend peut-être aussi du processeur, et il est rare de voir des chaînes avec de l’espace réservé supplémentaire)

C'était finalement un peu plus rapide que std::ostringstream mais il y a peu d'inconvénients:

  • J'ai supposé des types de caractères de longueur fixe (donc 1,2 ou 4 octets, ce qui n'est pas bon pour UTF8), je ne dis pas que cela ne fonctionnera pas pour UTF8, je ne l'ai pas vérifiée pour la paresse.
  • J'ai utilisé une mauvaise pratique de codage (tampon opaque, facile à rendre non portable, je pense que le mien est portable d'ailleurs)
  • Manque toutes les fonctionnalités de ostringstream
  • Si une chaîne référencée est supprimée avant la fusion de toutes les chaînes: comportement indéfini.

conclusion? utiliser std::ostringstream

Cela corrige déjà le plus gros goulot d'étranglement, alors que le gain de quelques points de vitesse avec la mise en œuvre de la mine ne vaut pas les inconvénients.

0
GameDeveloper