c++ c++11 stdstring

c++ - ¿Hay desventajas de usar std:: string como búfer?



c++11 stdstring (7)

¿Te acuerdas de un puntero const? AFAIK esto no hace daño siempre que sepamos lo que hacemos, pero ¿es este un buen comportamiento y por qué?

El código actual puede tener un comportamiento indefinido, dependiendo de la versión de C ++. Para evitar un comportamiento indefinido en C ++ 14 e inferior, tome la dirección del primer elemento. Produce un puntero no constante:

buff.resize(size); memcpy(&buff[0], &receiver[0], size);

Recientemente he visto a un colega mío usando std::string como búfer ...

Eso fue algo común en el código antiguo, especialmente alrededor de C ++ 03. Hay varios beneficios y desventajas de usar una cadena como esa. Dependiendo de lo que esté haciendo con el código, std::vector puede ser un poco anémico, y en ocasiones usó una cadena en su lugar y aceptó la sobrecarga adicional de char_traits .

Por ejemplo, std::string suele ser un contenedor más rápido que std::vector en append, y no puede devolver std::vector desde una función. (O no podría hacerlo en la práctica en C ++ 98 porque C ++ 98 requería que el vector se construya en la función y se copie). Además, std::string le permitió buscar con una variedad más rica de funciones miembro, como find_first_of y find_first_not_of . Eso fue conveniente al buscar a través de matrices de bytes.

Creo que lo que realmente quieres / necesitas es la clase de cuerda de SGI, pero nunca llegó a la STL. Parece que libstdc++ GCC puede proporcionarlo.

Hay una larga discusión acerca de que esto es legal en C ++ 14 y más abajo:

const char* dst_ptr = buff.data(); const char* src_ptr = receiver.data(); memcpy((char*) dst_ptr, src_ptr, size);

Sé con certeza que no es seguro en GCC. Una vez hice algo como esto en algunas pruebas automáticas y resultó en una falla de seguridad:

std::string buff("A"); ... char* ptr = (char*)buff.data(); size_t len = buff.size(); ptr[0] ^= 1; // tamper with byte bool tampered = HMAC(key, ptr, len, mac);

GCC puso el único byte ''A'' en el registro AL . Los 3 bytes altos fueron basura, por lo que el registro de 32 bits fue 0xXXXXXX41 . Cuando hice referencia a ptr[0] , GCC hizo referencia a una dirección de basura 0xXXXXXX41 .

Los dos puntos para mí fueron, no escriban autocomprobaciones a medias, y no intente hacer de los data() un puntero no constante.

Recientemente he visto a un colega mío usando std::string como búfer:

std::string receive_data(const Receiver& receiver) { std::string buff; int size = receiver.size(); if (size > 0) { buff.resize(size); const char* dst_ptr = buff.data(); const char* src_ptr = receiver.data(); memcpy((char*) dst_ptr, src_ptr, size); } return buff; }

Supongo que este tipo quiere aprovechar la destrucción automática de la cadena devuelta, por lo que no debe preocuparse por liberar el búfer asignado.

Esto me parece un poco extraño ya que, según cplusplus.com el método data() devuelve un const char* apunta a un búfer administrado internamente por la cadena:

const char* data() const noexcept;

¿Te acuerdas de un puntero const ? AFAIK esto no hace daño siempre que sepamos lo que hacemos, pero ¿me he perdido algo? ¿Es esto peligroso?


No use std::string como un búfer.

Es una mala práctica usar std::string como un búfer, por varias razones (enumeradas en ningún orden en particular):

  • std::string no fue diseñado para usarse como un búfer; tendría que volver a verificar la descripción de la clase para asegurarse de que no haya "errores" que impidan ciertos patrones de uso (o hacer que desencadenen un comportamiento indefinido).
  • Específicamente: antes de C ++ 17, cppreference través del puntero que obtiene con los data() , es const Tchar * ; por lo que su código causaría un comportamiento indefinido. (Pero &(str[0]) , &(str.front()) , o &(*(str.begin())) funcionaría.)
  • El uso de std::string ''s para buffers es confuso para los lectores de la implementación, quienes suponen que usted usaría std::string para, bueno, cadenas.
  • Peor aún, es confuso para quienquiera que use esta función: ellos también pueden pensar que lo que está devolviendo es una cadena, es decir, un texto válido que puede ser leído por humanos.
  • std::unique_ptr estaría bien para su caso, o incluso std::vector . En C ++ 17, también puede usar std::byte para el tipo de elemento. Una opción más sofisticada es una clase con una función similar a SSO , por ejemplo, small_vector de Boost (gracias, @ gast128, por mencionarlo).
  • (Punto menor :) libstdc ++ tuvo que cambiar su ABI para std::string para cumplir con el estándar C ++ 11, que en algunos casos (que ahora es bastante improbable), puede que tenga algunos problemas de vinculación o tiempo de ejecución que No lo haría con un tipo diferente para su búfer.

Además, su código puede hacer dos asignaciones en lugar de una pila (depende de la implementación): una vez que se construye la cadena y otra cuando se resize() ing. Pero eso en sí mismo no es realmente una razón para evitar std::string , ya que puedes evitar la doble asignación usando la construcción en la respuesta de @ Jarod42 .


Desde C ++ 17, los data pueden devolver un carácter no const char * .

El borrador n4659 declara en [string.accessors]:

const charT* c_str() const noexcept; const charT* data() const noexcept; .... charT* data() noexcept;


El código es innecesario, considerando que

std::string receive_data(const Receiver& receiver) { std::string buff; int size = receiver.size(); if (size > 0) { buff.assign(receiver.data(), size); } return buff; }

Hará exactamente lo mismo.


La gran oportunidad de optimización que investigaría aquí es: Receiver parece ser un tipo de contenedor que admite .data() y .size() . Si puede consumirlo, y pasarlo como un valor de referencia Receiver&& , ¡puede usar la semántica de movimientos sin hacer ninguna copia! Si tiene una interfaz de iterador, puede usarlos para constructores basados ​​en rango o std::move() de <algorithm> .

En C ++ 17 (como Serge Ballesta y otros han mencionado), std::string::data() devuelve un puntero a datos no constantes. Se ha garantizado que std::string almacena todos sus datos de forma contigua durante años.

El código como escrito huele un poco, aunque en realidad no es culpa del programador: esos trucos eran necesarios en ese momento. Hoy, al menos debe cambiar el tipo de dst_ptr de const char* a char* y eliminar la memcpy() en el primer argumento a memcpy() . También puede reserve() una cantidad de bytes para el búfer y luego usar una función STL para mover los datos.

Como han mencionado otros, un std::vector o std::unique_ptr sería una estructura de datos más natural para usar aquí.


Puedes evitar completamente un memcpy manual llamando al constructor apropiado:

std::string receive_data(const Receiver& receiver) { return {receiver.data(), receiver.size()}; }

Que incluso maneja /0 en una cadena.

Por cierto, a menos que el contenido sea realmente texto, preferiría std::vector<std::byte> (o equivalente).


Un inconveniente es el rendimiento. El método .resize inicializará por defecto todas las nuevas ubicaciones de bytes a 0. Esa inicialización no es necesaria si luego va a sobrescribir los 0s con otros datos.