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()
, esconst 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íastd::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 inclusostd::vector
. En C ++ 17, también puede usarstd::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.