¿Por qué no se permite modificar una cadena a través de un puntero recuperado a sus datos?

En C ++ 11, los caracteres de una std::string deben almacenarse contiguamente, como lo indica el § 21.4.1 / 5:

Los objetos tipo char en un objeto basic_string se deben almacenar contiguamente. Es decir, para cualquier objeto basic_string s, la identidad & * (s.begin () + n) == & * s.begin () + n se mantendrá para todos los valores de n tales que 0 <= n <s.size ().

Sin embargo, así es como el § 21.4.7.1 enumera las dos funciones para recuperar un puntero al almacenamiento subyacente (énfasis mío):

const charT * c_str () const noexcept;
const charT * data () const noexcept;
1 Devuelve: Un puntero p tal que p + i == & operador [] (i) para cada i en [0, tamaño ()].
2 Complejidad: tiempo constante.
3 Requiere: El progtwig no alterará ninguno de los valores almacenados en la matriz de caracteres.

Una posibilidad que se me ocurre para el punto número 3 es que el puntero puede quedar invalidado por los siguientes usos del objeto (§ 21.4.1 / 6):

  • como argumento para cualquier función de biblioteca estándar tomando como referencia una cadena básica no const como argumento.
  • Llamar a funciones miembro no constantes, excepto operator [], at, front, back, begin, rbegin, end y rend.

Aun así, los iteradores pueden invalidarse, pero aún podemos modificarlos independientemente hasta que lo hagan. Todavía podemos usar el puntero hasta que se invalide para leer también del buffer.

¿Por qué no podemos escribir directamente en este buffer? ¿Es porque pondría la clase en un estado inconsistente, ya que, por ejemplo, end() no se actualizaría con el nuevo extremo? Si es así, ¿por qué está permitido escribir directamente en el búfer de algo así como std::vector ?

Los casos de uso para esto incluyen poder pasar el búfer de std::string a una interfaz C para recuperar una cadena en lugar de pasar un vector lugar e inicializar la cadena con iteradores a partir de eso:

 std::string text; text.resize(GetTextLength()); GetText(text.data()); 

¿Por qué no podemos escribir directamente en este buffer?

Declararé el punto obvio: porque es const . Y descartar un valor const y luego modificar esos datos es … grosero.

Ahora, ¿por qué es const ? Eso se remonta a los días en que se consideraba que copiar con escritura era una buena idea, por lo que std::basic_string tenía que permitir que las implementaciones lo std::basic_string . Sería muy útil obtener un puntero inmutable para la cadena (para pasar a C-API, por ejemplo) sin incurrir en la sobrecarga de una copia. Entonces c_str necesitaba devolver un puntero const .

En cuanto a por qué todavía está const ? Bueno … eso va a una cosa extraña en el estándar: el terminador nulo.

Este es un código legítimo:

 std::string stupid; const char *pointless = stupid.c_str(); 

pointless debe ser una cadena terminada en NUL. Específicamente, debe ser un puntero a un carácter NUL. Entonces, ¿de dónde viene el personaje NUL? Hay varias maneras para que una implementación de std::string permita que esto funcione:

  1. Utilice la optimización de cadenas pequeñas, que es una técnica común. En este esquema, cada implementación de std::string tiene un búfer interno que puede usar para un único carácter NUL.
  2. Devuelve un puntero a la memoria estática , que contiene un carácter NUL. Por lo tanto, cada implementación de std::string devolverá el mismo puntero si es una cadena vacía.

No se debería forzar a todos a implementar SSO. Entonces el comité de estándares necesitaba una forma de mantener el # 2 sobre la mesa. Y parte de eso te está dando una cadena const desde c_str() . Y dado que es probable que esta memoria sea real , no es falso “No modifique esta const memoria”, es una mala idea proporcionarle un puntero mutable.

Por supuesto, todavía puede obtener dicho puntero haciendo &str[0] , pero el estándar es muy claro que la modificación del terminador NUL es una mala idea .

Ahora bien, dicho esto, es perfectamente válido modificar el puntero &str[0] y la matriz de caracteres que contiene. Siempre y cuando permanezcas en el rango medio abierto [0, str.size() ). Simplemente no puede hacerlo a través del puntero devuelto por data o c_str . Sí, aunque el estándar de hecho requiere que str.c_str() == &str[0] sea ​​verdadero.

Eso es estándar para usted.