¿Hay una clase de C ++ Biblioteca de plantillas estándar que proporciona la funcionalidad de concatenación de cadenas eficiente, similar a C # 's StringBuilder o de Java StringBuffer ?
¿Hay una clase de C ++ Biblioteca de plantillas estándar que proporciona la funcionalidad de concatenación de cadenas eficiente, similar a C # 's StringBuilder o de Java StringBuffer ?
Respuestas:
NOTA: esta respuesta ha recibido alguna atención recientemente. No estoy abogando por esto como una solución (es una solución que he visto en el pasado, antes de la STL). Es un enfoque interesante y solo debe aplicarse sobre std::string
ostd::stringstream
si después de perfilar su código descubre que esto mejora.
Normalmente uso cualquiera std::string
ostd::stringstream
. Nunca he tenido ningún problema con estos. Normalmente reservaría algo de espacio primero si supiera de antemano el tamaño aproximado de la cuerda.
He visto a otras personas hacer su propio generador de cadenas optimizado en el pasado 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;
}
};
Utiliza dos cadenas, una para la mayoría de la cadena y la otra como un área de scratch para concatenar cadenas cortas. Optimiza los agregados al agrupar las operaciones de agregado corto en una cadena pequeña y luego agregar esto a la cadena principal, reduciendo así el número de reasignaciones requeridas en la cadena principal a medida que se hace más grande.
No he requerido este truco con std::string
o std::stringstream
. Creo que se usó con una biblioteca de cadenas de terceros antes de std :: string, fue hace mucho tiempo. Si adopta una estrategia como este perfil, su aplicación primero.
scratch
cadena realmente logre nada aquí. El número de reasignaciones de la cadena principal dependerá en gran medida de su tamaño final, no del número de operaciones de adición, a menos que la string
implementación sea realmente pobre (es decir, no utilice un crecimiento exponencial). Así que "agrupar" append
no ayuda porque una vez que el subyacente string
es grande, solo crecerá ocasionalmente de cualquier manera. Además de eso, agrega un montón de operaciones de copia redundantes y puede haber más reasignaciones (por lo tanto, llamadas a new
/ delete
) ya que está agregando una cadena corta.
str.reserve(1024);
que sería más rápido que esta cosa
La forma en C ++ sería usar std :: stringstream o simplemente concatenaciones de cadenas simples. Las cadenas de C ++ son mutables, por lo que las consideraciones de rendimiento de la concatenación son menos preocupantes.
con respecto al formateo, puede hacer el mismo formateo en una secuencia, pero de una manera diferente, similar acout
. o puede usar un functor fuertemente tipado que encapsula esto y proporciona una interfaz tipo String.Format, por ejemplo, boost :: format
StringBuilder
existe es para cubrir la ineficiencia del tipo de cadena básica inmutable de Java . En otras palabras, StringBuilder
es un mosaico, por lo que deberíamos estar contentos de no necesitar tal clase en C ++.
O(n)
en general.
La std::string.append
función no es una buena opción porque no acepta muchas formas de datos. Una alternativa más útil es usar std::stringstream
; al igual que:
#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();
Puede usar .append () para simplemente concatenar cadenas.
std::string s = "string1";
s.append("string2");
Creo que incluso podrías hacer:
std::string s = "string1";
s += "string2";
En cuanto a las operaciones de formateo de C # 's StringBuilder
, creo que snprintf
(o sprintf
si quieres arriesgarte a escribir un código defectuoso ;-)) en una matriz de caracteres y volver a convertirlo en una cadena es la única opción.
Como std::string
en C ++ es mutable, puede usar eso. Tiene un += operator
y unappend
función.
Si necesita agregar datos numéricos, use el std::to_string
funciones.
Si desea aún más flexibilidad en la forma de poder serializar cualquier objeto en una cadena, use la std::stringstream
clase. Pero necesitará implementar sus propias funciones de operador de transmisión para que funcione con sus propias clases personalizadas.
Un conveniente generador de cadenas para c ++
Como muchas personas respondieron antes, std :: stringstream es el método de elección. Funciona bien y tiene muchas opciones de conversión y formato. Sin embargo, en mi opinión, tiene un defecto bastante inconveniente: no puedes usarlo como una sola línea o como una expresión. Siempre tienes que escribir:
std::stringstream ss;
ss << "my data " << 42;
std::string myString( ss.str() );
lo cual es bastante molesto, especialmente cuando quieres inicializar cadenas en el constructor.
La razón es que a) std :: stringstream no tiene operador de conversión a std :: string yb) los operadores << () del stringstream no devuelven una referencia de stringstream, sino una referencia std :: ostream - que no se puede calcular más como una secuencia de cadena.
La solución es anular std :: stringstream y darle mejores operadores de coincidencia:
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;
}
Con esto, puedes escribir cosas como
std::string myString( NsStringBuilder::stringstream() << "my data " << 42 )
incluso en el constructor.
Tengo que confesar que no medí el rendimiento, ya que todavía no lo he usado en un entorno que hace un uso intensivo de la construcción de cadenas, pero supongo que no será mucho peor que std :: stringstream, ya que todo está hecho a través de referencias (excepto la conversión a cadena, pero también es una operación de copia en std :: stringstream)
std::stringstream
no se comporta de esta manera.
El contenedor Rope puede valer si tiene que insertar / eliminar una cadena en el lugar aleatorio de la cadena de destino o para una secuencia de caracteres larga. Aquí hay un ejemplo de la implementación 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.
Quería agregar algo nuevo por lo siguiente:
En un primer intento no pude vencer
std::ostringstream
's operator<<
eficiencia, pero con más intentos pude hacer un StringBuilder que es más rápido en algunos casos.
Cada vez que agrego una cadena, solo almaceno una referencia en ella en algún lugar y aumento el contador del tamaño total.
La forma real en que finalmente lo implementé (¡Horror!) Es usar un búfer opaco (std :: vector <char>):
para el byte []
para cadenas movidas (cadenas agregadas con std::move
)
std::string
objeto (tenemos propiedad)para cuerdas
std::string
objeto (sin propiedad)También hay una pequeña optimización, si la última cadena insertada se movió, comprueba los bytes libres reservados pero no utilizados y almacena más bytes allí en lugar de usar el búfer opaco (esto es para ahorrar algo de memoria, en realidad lo hace un poco más lento , tal vez dependa también de la CPU, y de todos modos es raro ver cadenas con espacio reservado adicional)
Finalmente, esto fue un poco más rápido que, std::ostringstream
pero tiene algunas desventajas:
ostringstream
¿conclusión? utilizar
std::ostringstream
Ya solucionó el mayor cuello de botella, mientras que obtener pocos puntos porcentuales de velocidad con la implementación de la mina no vale la pena.
std::ostringstream
.