C ++ equivalente a StringBuffer / StringBuilder?

184

Existe uma classe C ++ Standard Template Library que fornece funcionalidade eficiente de concatenação de cadeias, semelhante ao StringBuilder do C # ou ao StringBuffer do Java ?

An̲̳̳drew
fonte
3
a resposta curta é: Sim, a STL tem uma classe para isso e é std::ostringstream.
CoffeDeveloper 30/04
Hey @andrew. Você pode alterar a resposta aceita? Existe uma resposta clara e vencedora e não é a resposta atualmente aceita.
null

Respostas:

53

NOTA esta resposta recebeu alguma atenção recentemente. Não estou defendendo isso como uma solução (é uma solução que eu já vi no passado, antes do STL). É uma abordagem interessante e só deve ser aplicada sobre std::stringou std::stringstreamse, após criar um perfil do seu código, você descobrir que isso melhora.

Eu normalmente uso um std::stringou std::stringstream. Eu nunca tive problemas com isso. Normalmente, eu reservaria um espaço primeiro se soubesse o tamanho aproximado da corda com antecedência.

Vi outras pessoas criarem seu próprio construtor de strings 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 strings, uma para a maioria das strings e a outra como uma área de rascunho para concatenar strings curtas. Ele otimiza os anexos agrupando em lotes as operações de acréscimo curto em uma cadeia pequena e, em seguida, anexando-a à cadeia principal, reduzindo assim o número de realocações necessárias na cadeia principal à medida que ela aumenta.

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

iain
fonte
13
Reinventando a roda. std :: stringstream é a resposta correta. Veja boas respostas abaixo.
precisa saber é o seguinte
13
@ Kobor42 Eu concordo com você, como indico na primeira e na última linha da minha resposta.
iain
1
Eu não acho que a scratchcorda realmente realiza alguma coisa aqui. O número de realocações da cadeia principal será em grande parte uma função de seu tamanho final, não o número de operações de acréscimo, a menos que a stringimplementação seja realmente ruim (ou seja, não use crescimento exponencial). Portanto, "agrupar" o appendpacote não ajuda, porque, uma vez que o subjacente stringé grande, ele só cresce ocasionalmente de qualquer maneira. Além disso, ele adiciona várias operações de cópia redundante e pode ter mais realocações (daí chamadas para new/ delete), pois você está anexando uma sequência curta.
BeeOnRope
@BeeOnRope Concordo com você.
Iain
Tenho certeza que str.reserve(1024);seria mais rápido do que essa coisa
hanshenrik
160

A maneira C ++ seria usar std :: stringstream ou concatenações de strings simples. As seqüências de caracteres C ++ são mutáveis; portanto, as considerações de desempenho da concatenação são menos preocupantes.

no que diz respeito à formatação, você pode fazer a mesma formatação em um fluxo, mas de uma maneira diferente, semelhante acout . ou você pode usar um functor fortemente tipado que encapsula isso e fornece uma String.Format como interface, por exemplo, boost :: format

jk.
fonte
59
Cadeias de caracteres C ++ são mutáveis : exatamente. Todo o motivo StringBuilderexiste é para cobrir a ineficiência do tipo String imutável básico do Java . Em outras palavras, StringBuilderé uma colcha de retalhos; portanto, devemos estar felizes por não precisarmos dessa classe em C ++.
bobobobo
57
As cordas imutáveis ​​do @obobobo têm outros benefícios, porém, seus cavalos para percursos
jk.
8
As concatenações de strings simples não criam um novo objeto, portanto, o mesmo problema da imutabilidade em Java? Considere que todas as variáveis ​​são cadeias de caracteres no exemplo a seguir: a = b + c + d + e + f; Não vai chamar o operador + nos bec, depois o operador + no resultado ed etc.?
Serge Rogatch
9
Espere um pouco, pessoal, a classe de string padrão sabe como se transformar, mas isso não significa que a ineficiência não está lá. Tanto quanto sei, std :: string não pode simplesmente estender o tamanho de seu caractere interno *. Isso significa que mutá-lo de uma maneira que exija mais caracteres requer uma realocação e cópia. Não é diferente de um vetor de caracteres e, certamente, é melhor reservar o espaço necessário nesse caso.
Trygve Skogsholm
7
@TrygveSkogsholm - não é diferente de um vetor de caracteres, mas é claro que a "capacidade" da string pode ser maior que seu tamanho, portanto, nem todos os anexos precisam de uma realocação. Em geral, as strings usarão uma estratégia de crescimento exponencial, de modo que o anexo ainda seja amortizado em uma operação de custo linear. Isso é diferente das imutáveis ​​Strings do Java, nas quais toda operação de acréscimo precisa copiar todos os caracteres em ambas as Strings para uma nova, portanto, uma série de acréscimos termina como O(n)em geral.
BeeOnRope
93

A std::string.appendfunção não é uma boa opção porque não aceita muitas formas de dados. Uma alternativa mais útil é usar std::stringstream; igual a:

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

std::string é o equivalente em C ++: é mutável.

dan04
fonte
13

Você pode usar .append () para simplesmente concatenar seqüências de caracteres.

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

Eu acho que você pode até fazer:

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

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

Andy Shellam
fonte
Não da mesma maneira que o printf ou o String.Format do .NET, são?
Andy Shellam
1
é um pouco falso dizer que eles são a única maneira
jk.
2
@jk - eles são a única maneira de comparar a capacidade de formatação do StringBuilder do .NET, que é a pergunta original especificamente feita. Eu disse "eu acredito" para que eu pudesse estar errado, mas você pode me mostrar uma maneira de obter a funcionalidade do StringBuilder em C ++ sem usar o printf?
Andy Shellam
atualizei minha resposta para incluir algumas opções alternativas de formatação
jk.
6

Como std::stringno C ++ é mutável, você pode usá-lo. Tem += operatoruma appendfunção e.

Se você precisar acrescentar dados numéricos, use as std::to_stringfunções.

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

Daemin
fonte
4

std :: string + = não funciona com const char * (o que parece ser "string a adicionar"), então definitivamente usar stringstream é o mais próximo do que é necessário - basta usar << em vez de +

sergeys
fonte
3

Um construtor de strings conveniente para c ++

Como muitas pessoas responderam antes, std :: stringstream é o método de escolha. Funciona bem e possui muitas opções de conversão e formatação. IMO, porém, tem uma falha bastante inconveniente: você não pode usá-lo como um liner ou como uma 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 seqüências de caracteres no construtor.

O motivo é que a) std :: stringstream não tem operador de conversão para std :: string eb) os operadores << () do stringstream não retornam uma referência stringstream, mas uma referência std :: ostream - que não pode mais ser computado como um fluxo de strings.

A solução é substituir o std :: stringstream e oferecer melhores 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.

Tenho que confessar que não medi o desempenho, pois ainda não o usei em um ambiente que faça uso pesado da construção de strings, mas presumo que não será muito pior do que std :: stringstream, pois tudo está feito via referências (exceto a conversão em string, mas isso também é uma operação de cópia em std :: stringstream)

user2328447
fonte
Isso é legal. Não vejo por std::stringstreamque não se comporta dessa maneira.
einpoklum 07/06
1

O contêiner Rope pode valer a pena se for necessário inserir / excluir uma string no local aleatório da string de destino ou por longas sequências de caracteres. 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.
Igor
fonte
0

Eu queria adicionar algo novo por causa do seguinte:

Numa primeira tentativa, não consegui vencer

std::ostringstream é operator<<

eficiência, mas com mais tentativas, consegui criar um StringBuilder que é mais rápido em alguns casos.

Sempre que anexo uma string, apenas guardo uma referência a ela em algum lugar e aumento o contador do tamanho total.

A maneira real de finalmente implementá-lo (Horror!) É usar um buffer opaco (std :: vector <char>):

  • Cabeçalho de 1 byte (2 bits para dizer se os seguintes dados são: string movida, string ou byte [])
  • 6 bits para dizer o comprimento do byte []

para byte []

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

para cadeias movidas (cadeias anexadas com std::move)

  • O ponteiro para um std::stringobjeto (temos propriedade)
  • defina um sinalizador na classe se houver bytes reservados não utilizados lá

para cordas

  • O ponteiro para um std::stringobjeto (sem propriedade)

Há também uma pequena otimização: se a última string inserida foi movida, ela verifica se há bytes reservados reservados, mas não utilizados, livres e armazena mais bytes lá em vez de usar o buffer opaco (isso é para economizar memória, na verdade, fica um pouco mais lento , talvez dependa também da CPU, e é raro ver sequências com espaço reservado extra de qualquer maneira)

Finalmente, isso foi um pouco mais rápido do que, std::ostringstreammas tem poucas desvantagens:

  • Eu assumi tipos de caracteres de comprimento fixo (portanto, 1,2 ou 4 bytes, não é bom para UTF8), não estou dizendo que não funcionará para UTF8, apenas não verifiquei por preguiça.
  • Eu usei práticas ruins de codificação (buffer opaco, fácil de torná-lo não portátil, acredito que o meu seja portátil)
  • Não possui todos os recursos do ostringstream
  • Se alguma string referenciada for excluída antes da mesclagem de todas as strings: comportamento indefinido.

conclusão? usar std::ostringstream

Ele já corrige o maior gargalo, enquanto ganhar alguns pontos percentuais em velocidade com a implementação da mina não vale a pena.

CoffeDeveloper
fonte