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 ?


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.


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


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

43

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


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.


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.


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 +


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)


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.

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.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.