Pergunta

Existe uma classe de biblioteca de modelo padrão C ++ que fornece funcionalidade eficiente de concatenação de strings, semelhante à C#'s StringBuilder ou Java's StringBuffer?

Foi útil?

Solução

Observe que esta resposta recebeu alguma atenção recentemente. Não estou defendendo isso como uma solução (é uma solução que já vi no passado, antes do STL). É uma abordagem interessante e só deve ser aplicada std::string ou std::stringstream Se depois de criar seu código, você descobrirá isso melhorar.

Eu normalmente uso também std::string ou std::stringstream. Eu nunca tive problemas com eles. Normalmente, eu reservaria algum espaço primeiro se soubesse o tamanho aproximado da corda com antecedência.

Vi outras pessoas fazer seu próprio construtor de cordas otimizado no passado distante.

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;
    }
};

Ele usa duas cordas uma para a maioria da corda e a outra como uma área de arranhões para concatenar cordas curtas. Ele otimiza os anexos, em lote as operações de anexo curtas em uma pequena string e depois anexando -a à sequência principal, reduzindo assim o número de realações necessárias na sequência principal, à medida que aumenta.

Eu não exigi esse truque com std::string ou std::stringstream. Eu acho que foi usado com uma biblioteca de string de terceiros antes da STD :: String, foi há muito tempo. Se você adotar uma estratégia como este perfil, seu aplicativo primeiro.

Outras dicas

A maneira C ++ seria usar std :: stringStream ou apenas concatenações simples de string. As cordas de C ++ são mutáveis, portanto as considerações de desempenho da concatenação são menos preocupantes.

Com relação à formatação, você pode fazer a mesma formatação em um fluxo, mas de uma maneira diferente, semelhante a cout. Ou você pode usar um functor fortemente digitado que encapsula isso e fornece uma string.format como interface, por exemplo Boost :: formato

A função STD :: String.Append não é uma boa opção porque não aceita muitas formas de dados. Uma alternativa mais útil é usar o std: stringStream, assim:

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

std::string é O equivalente C ++: é mutável.

Você pode usar .Append () para simplesmente concatenar strings.

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

Eu acho que você pode até ser capaz de fazer:

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

Quanto às operações de formatação de C#' StringBuilder, Eu acredito snprintf (ou sprintf Se você deseja arriscar escrever código de buggy ;-)) em uma matriz de caracteres e converter novamente em uma string é a única opção.

Desde std::string No C ++ é mutável, você pode usar isso. Tem um += operator e um append função.

Se você precisar anexar dados numéricos, use o std::to_string funções.

Se você deseja ainda mais flexibilidade na forma de ser capaz de serializar qualquer objeto a uma string, use o std::stringstream classe. Mas você precisará implementar suas próprias funções do operador de streaming para que ele funcione com suas próprias classes personalizadas.

std :: string's += não funciona com const char* (que coisas como "string to adicionar" parecem ser), então definitivamente usar o stringStream é o mais próximo do que é necessário - você apenas usa << em vez de +

Um construtor de cordas conveniente para C ++

Como muitas pessoas responderam antes, o STD :: StringStream é o método de escolha. Funciona bem e tem muitas opções de conversão e formatação. IMO, porém, ele tem uma falha bastante inconveniente: você não pode usá -la como um revestimento ou como expressão. Você sempre tem que escrever:

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

O que é bastante irritante, especialmente quando você deseja inicializar strings no construtor.

O motivo é que a) std :: stringStream não tem operador de conversão para std :: string e b) o operador << () do stringream não retorna uma referência de greve, mas uma referência de Ostream em vez disso. - que não pode ser mais calculado como um fluxo de string.

A solução é substituir o STD :: stringStream e dar a melhor os operadores correspondentes:

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;
}

Com isso, você pode escrever coisas como

std::string myString( NsStringBuilder::stringstream() << "my data " << 42 )

mesmo no construtor.

Eu tenho que confessar que não medi o desempenho, já que ainda não o usei em um ambiente que ainda faz uso pesado da construção de cordas, mas presumo que não será muito pior do que o std :: stringStream, já que tudo é feito via referências (exceto a conversão para string, mas essa é uma operação de cópia em std :: stringStream)

o Corda O contêiner pode valer a pena se precisar inserir/excluir a string no local aleatório da string de destino ou para uma longa seqüências de char. Aqui está um exemplo da implementação da 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.

Eu queria adicionar algo novo por causa do seguinte:

Na primeira tentativa, não consegui vencer

std::ostringstream 's operator<<

Eficiência, mas com mais tentativas, pude fazer um StringBuilder que é mais rápido em alguns casos.

Toda vez que eu anexo uma string, apenas guardo uma referência a ela em algum lugar e aumentou o contador do tamanho total.

A verdadeira maneira de eu finalmente implementar (horror!) É usar um buffer opaco (STD :: Vector <Char>):

  • 1 Cabeçalho de byte (2 bits para saber se os seguintes dados são: moved string, string ou byte [])
  • 6 bits para contar a Lenght de Byte [

para byte [

  • Eu guardo bytes diretamente de cordas curtas (para acesso seqüencial à memória)

Para cordas movidas (Strings anexados com std::move)

  • O ponteiro para um std::string objeto (temos propriedade)
  • Defina uma bandeira na classe se houver bytes reservados não utilizados lá

para cordas

  • O ponteiro para um std::string objeto (sem propriedade)

Há também uma pequena otimização, se a última string inserida foi movida, ela verifica bytes reservados, mas não utilizados gratuitos, e armazenam mais bytes lá em vez de usar o buffer opaco (isso é para salvar alguma memória, ele realmente o torna um pouco mais lento , talvez dependa também da CPU, e é raro ver cordas com espaço extra reservado de qualquer maneira)

Isso finalmente foi um pouco mais rápido do que std::ostringstream Mas tem poucas desvantagens:

  • Presumi que os tipos fixos de char (então 1,2 ou 4 bytes, não bons para o UTF8), não estou dizendo que não funcionará para o UTF8, apenas não o verifiquei por preguiça.
  • Eu usei a prática de codificação ruim (buffer opaco, fácil de torná -lo não portátil, acredito que o meu é portátil a propósito)
  • Não tem todas as características de ostringstream
  • Se alguma string referenciada for excluída antes da Mergin todas as cordas: comportamento indefinido.

conclusão? usarstd::ostringstream

Ele já corrige o maior gargalo, enquanto os pontos de velocidade em velocidade de velocidade com a implementação das minas não valem as desvantagens.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top