C++ équivalent de StringBuffer / StringBuilder?

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

135
demandé sur An̲̳̳drew 2010-03-17 17:20:22

10 réponses

Notez que cette réponse a reçu une certaine attention récemment. Je ne préconise pas cela comme une solution (c'est une solution que j'ai vue dans le passé, avant la STL). C'est une approche intéressante et ne devrait être appliquée que sur std::string ou {[2] } si, après avoir profilé votre code, vous découvrez que cela apporte une amélioration.

J'ai l'habitude d'utiliser soit l'std::string ou std::stringstream. Je n'ai jamais eu de problèmes avec ces. Je réserverais normalement un peu de place d'abord si je connais le rugueux taille de la chaîne à l'avance.

J'ai vu d'autres personnes créer leur propre générateur de chaînes 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 cordes l'une pour la majorité de la chaîne et l'autre comme zone de grattage pour concaténer des cordes courtes. Il optimise les ajouts en regroupant les opérations d'ajout courtes dans une petite chaîne, puis en l'ajoutant à la chaîne principale, réduisant ainsi le nombre de réallocations requises sur la chaîne principale à mesure qu'elle grossit.

Je n'ont pas exigé 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 celle-ci, profilez d'abord votre application.

37
répondu iain 2013-11-25 17:16:19

La manière c++ serait d'utiliser std::stringstream ou simplement des concaténations de chaînes simples. Les chaînes C++ sont mutables, donc les considérations de performance de la concaténation sont moins préoccupantes.

En ce qui concerne le formatage, vous pouvez faire 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 chaîne.Format comme interface, par exemple, boost::format

121
répondu jk. 2013-09-06 12:40:18

La chaîne std::.ajouter la fonction n'est pas une bonne option car elle n'accepte pas de nombreuses formes de données. Une alternative plus utile est d'utiliser std: stringstream, comme ceci:

#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();
75
répondu Stu 2015-08-12 16:52:58

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

34
répondu dan04 2010-06-09 13:30:43

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 capable de faire:

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

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

9
répondu Andy Shellam 2010-03-17 14:23:14

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

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

Si vous voulez encore plus de flexibilité sous la forme de pouvoir sérialiser n'importe quel objet sur une chaîne, utilisez la classe std::stringstream. Mais vous devrez implémenter vos propres fonctions d'opérateur de streaming pour qu'il fonctionne avec vos propres classes personnalisées.

5
répondu Daemin 2011-09-29 02:08:13

Std:: string's += NE FONCTIONNE PAS avec const char * (ce que des choses comme "string to add" semblent être), donc l'utilisation de stringstream est la plus proche de ce qui est requis-vous utilisez simplement

2
répondu sergeys 2011-10-27 12:19:06

Le conteneur Rope peut être utile si vous devez insérer / supprimer une chaîne dans le lieu 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.
1
répondu Igor 2014-01-24 15:42:15

Je voulais ajouter quelque chose de nouveau à cause de ce qui suit:

Lors d'un premier attemp, j'ai échoué à battre

std::ostringstream 's operator<<

Efficacité, mais avec plus de tentatives, j'ai pu créer un StringBuilder plus rapide dans certains cas.

Chaque fois que j'ajoute une chaîne, je stocke juste 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 ):

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

pour octet [ ]

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

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

  • le pointeur vers un objet std::string (nous avons la propriété)
  • définit un indicateur dans la classe s'il n'y a pas octets là

pour les chaînes

  • le pointeur vers 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 les octets réservés mais inutilisés et stocke d'autres octets au lieu d'utiliser le tampon opaque (c'est pour économiser de la mémoire, cela le rend légèrement plus lent, peut-être dépend aussi du CPU, et il est rare de voir des chaînes avec de toute façon)

C'était finalement légèrement plus rapide que std::ostringstream mais il a peu d'inconvénients:

  • j'ai supposé des types de caractères de longueur fixe (donc 1,2 ou 4 octets, pas bon pour UTF8), Je ne dis pas que cela ne fonctionnera pas pour UTF8, mais je ne l'ai pas vérifié pour la paresse.
  • j'ai utilisé une mauvaise pratique de codage (tampon opaque, facile à rendre non portable, je crois que le mien est portable en passant)
  • manque de toutes les fonctionnalités de ostringstream
  • si une chaîne référencée est supprimée avant de fusionner tous les chaînes: comportement indéfini.

Conclusion? utiliser std::ostringstream

Il corrige déjà le plus gros goulot d'étranglement tout en ganing quelques % de points de vitesse avec la mise en œuvre de la mine ne vaut pas les inconvénients.

0
répondu GameDeveloper 2015-04-30 12:55:05

Un générateur de chaîne pratique pour c++

Comme beaucoup de gens ont répondu auparavant, std::stringstream est la méthode de choix. Il fonctionne bien et a beaucoup d'options de conversion et de formatage. IMO il a un défaut assez gênant cependant: vous ne pouvez pas l'utiliser comme une doublure ou comme une expression. Vous devez toujours écrire:

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

Ce qui est assez ennuyeux, surtout quand vous voulez 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

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, puisque j'ai pas utilisé dans un environnement qui fait encore un usage intensif de 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 aussi une opération de copie dans std::stringstream)

0
répondu user2328447 2017-11-02 01:32:00