Eu ouvi algumas pessoas expressando preocupações sobre o operador "+" em std :: string e várias soluções alternativas para acelerar a concatenação. Algum desses é realmente necessário? Em caso afirmativo, qual é a melhor maneira de concatenar strings em C ++?
108
libstdc++
faz isso, por exemplo . Portanto, ao chamar o operador + com temporários, ele pode atingir um desempenho quase tão bom - talvez um argumento a favor de padronizar para ele, por uma questão de legibilidade, a menos que haja benchmarks mostrando que é um gargalo. No entanto, uma variável padronizadaappend()
seria ótima e legível ...Respostas:
O trabalho extra provavelmente não vale a pena, a menos que você realmente precise de eficiência. Você provavelmente terá uma eficiência muito melhor simplesmente usando operator + = em vez disso.
Agora, após esse aviso, vou responder sua pergunta real ...
A eficiência da classe de string STL depende da implementação de STL que você está usando.
Você pode garantir eficiência e ter maior controle fazendo a concatenação manualmente por meio de c funções integradas.
Por que operator + não é eficiente:
Dê uma olhada nesta interface:
Você pode ver que um novo objeto é retornado após cada +. Isso significa que um novo buffer é usado a cada vez. Se você estiver fazendo uma tonelada de operações extras +, isso não será eficiente.
Por que você pode torná-lo mais eficiente:
Considerações para implementação:
Estrutura de dados da corda:
Se você precisa de concatenações realmente rápidas, considere o uso de uma estrutura de dados de corda .
fonte
Reserve seu espaço final antes e, em seguida, use o método append com um buffer. Por exemplo, digamos que você espera que o comprimento final da string seja de 1 milhão de caracteres:
fonte
Eu não me preocuparia com isso. Se você fizer isso em um loop, as strings sempre pré-alocarão a memória para minimizar as realocações - apenas use
operator+=
nesse caso. E se você fizer manualmente, algo assim ou maisEntão está criando temporários - mesmo se o compilador pudesse eliminar algumas cópias de valor de retorno. Isso ocorre porque em um chamado sucessivamente,
operator+
ele não sabe se o parâmetro de referência faz referência a um objeto nomeado ou a um temporário retornado de uma suboperator+
invocação. Prefiro não me preocupar com isso antes de não ter feito o perfil primeiro. Mas vamos dar um exemplo para mostrar isso. Primeiro, introduzimos parênteses para tornar a ligação clara. Eu coloco os argumentos diretamente após a declaração da função que é usada para maior clareza. Abaixo disso, mostro qual é a expressão resultante:Agora, nessa adição,
tmp1
é o que foi retornado pela primeira chamada ao operador + com os argumentos mostrados. Assumimos que o compilador é realmente inteligente e otimiza a cópia do valor de retorno. Portanto, terminamos com uma nova string que contém a concatenação dea
e" : "
. Agora, isso acontece:Compare isso com o seguinte:
Ele está usando a mesma função para uma string temporária e para uma string nomeada! Portanto, o compilador precisa copiar o argumento em uma nova string e anexar a ela e retorná-la do corpo de
operator+
. Não pode tirar a memória de um temporário e anexar a isso. Quanto maior a expressão, mais cópias de strings precisam ser feitas.Em seguida, o Visual Studio e o GCC oferecerão suporte à semântica de movimentação de c ++ 1x (complementando a semântica de cópia ) e referências rvalue como uma adição experimental. Isso permite descobrir se o parâmetro faz referência a um temporário ou não. Isso tornará essas adições incrivelmente rápidas, já que todos os itens acima acabarão em um "add-pipeline" sem cópias.
Se for um gargalo, você ainda pode
As
append
chamadas acrescentam o argumento a*this
e, em seguida, retornam uma referência a si mesmas. Portanto, nenhuma cópia de temporários é feita lá. Ou, alternativamente, ooperator+=
pode ser usado, mas você precisaria de parênteses feios para fixar a precedência.fonte
libstdc++
paraoperator+(string const& lhs, string&& rhs)
fazreturn std::move(rhs.insert(0, lhs))
. Então, se ambos são temporários, o seuoperator+(string&& lhs, string&& rhs)
casolhs
tem capacidade suficiente disponível só vai diretamenteappend()
. Onde eu acho que isso corre o risco de ser mais lento do queoperator+=
selhs
não tiver capacidade suficiente, pois então ele volta pararhs.insert(0, lhs)
, o que não só deve estender o buffer e adicionar novos conteúdos comoappend()
, mas também precisa se deslocar ao longo do conteúdo original darhs
direita.operator+=
é queoperator+
ainda deve retornar um valor, portanto, paramove()
qualquer operando ao qual foi anexado. Ainda assim, acho que é uma sobrecarga bem menor (copiar alguns ponteiros / tamanhos) em comparação com copiar em profundidade a string inteira, então é bom!Para a maioria dos aplicativos, isso simplesmente não importa. Apenas escreva seu código, felizmente sem saber como exatamente o operador + funciona, e só resolva o problema com suas próprias mãos se isso se tornar um aparente gargalo.
fonte
Ao contrário do .NET System.Strings, std :: strings do C ++ são mutáveis e, portanto, podem ser construídas por meio de concatenação simples tão rápido quanto por meio de outros métodos.
fonte
operator+
não precisa retornar uma nova string. Os implementadores podem retornar um de seus operandos, modificado, se esse operando foi passado pela referência rvalue.libstdc++
faz isso, por exemplo . Portanto, ao chamaroperator+
com temporários, ele pode atingir o mesmo ou quase tão bom desempenho - o que pode ser outro argumento a favor do inadimplemento, a menos que haja benchmarks que mostrem que isso representa um gargalo.talvez std :: stringstream em vez disso?
Mas eu concordo com o sentimento de que você provavelmente deve apenas mantê-lo sustentável e compreensível e então criar um perfil para ver se você realmente está tendo problemas.
fonte
No Imperfect C ++ , Matthew Wilson apresenta um concatenador de string dinâmico que pré-calcula o comprimento da string final para ter apenas uma alocação antes de concatenar todas as partes. Também podemos implementar um concatenador estático brincando com modelos de expressão .
Esse tipo de ideia foi implementado na implementação STLport std :: string - que não está em conformidade com o padrão por causa deste hack preciso.
fonte
Glib::ustring::compose()
das ligações glibmm para o GLib faz isso: estimareserve()
es o comprimento final com base na string de formato fornecida e os varargs, entãoappend()
s cada (ou sua substituição formatada) em um loop. Imagino que seja uma forma bastante comum de trabalhar.std::string
operator+
aloca uma nova string e sempre copia as duas strings de operandos. repita muitas vezes e fica caro, O (n).std::string
append
e,operator+=
por outro lado, aumente a capacidade em 50% sempre que a corda precisar crescer. O que reduz significativamente o número de alocações de memória e operações de cópia, O (log n).fonte
operator+
onde um ou ambos os argumentos são passados pela referência rvalue podem evitar a alocação de uma nova string por concatenação no buffer existente de um dos operandos (embora eles possam ter que realocar se tiver capacidade insuficiente).Para cordas pequenas, isso não importa. Se você tiver strings grandes, é melhor armazená-las como estão em vetor ou em alguma outra coleção como partes. E adapte seu algoritmo para trabalhar com esse conjunto de dados em vez de uma grande string.
Eu prefiro std :: ostringstream para concatenação complexa.
fonte
Como a maioria das coisas, é mais fácil não fazer algo do que fazer.
Se você deseja gerar strings grandes para uma GUI, pode ser que o que quer que você esteja produzindo possa lidar com as strings em partes melhor do que em uma string grande (por exemplo, concatenando texto em um editor de texto - geralmente eles mantêm as linhas separadas estruturas).
Se você deseja enviar para um arquivo, transmita os dados em vez de criar uma string grande e gerá-la.
Nunca achei a necessidade de tornar a concatenação mais rápida necessária se removesse a concatenação desnecessária do código lento.
fonte
Provavelmente melhor desempenho se você pré-alocar (reservar) espaço na string resultante.
Uso:
fonte
Um array simples de caracteres, encapsulado em uma classe que controla o tamanho do array e o número de bytes alocados é o mais rápido.
O truque é fazer apenas uma grande alocação no início.
em
https://github.com/pedro-vicente/table-string
Benchmarks
Para Visual Studio 2015, compilação de depuração x86, melhoria substancial em relação a C ++ std :: string.
fonte
std::string
. Eles não estão pedindo uma classe de string alternativa.Você pode tentar este com reservas de memória para cada item:
fonte