¿Por qué no es un error del compilador asignar al resultado de una llamada substr?


Acabo de descubrir el error más desconcertante y no entiendo por qué el compilador no lo marcó para mí. Si escribo lo siguiente:

string s = "abcdefghijkl";
cout << s << endl;

s.substr(2,3) = "foo";
s.substr(8,1) = '.';
s.substr(9,1) = 4;
cout << s << endl;

El compilador no tiene ningún problema con esto, y las instrucciones de asignación parecen no tener ningún efecto, basado en lo que se imprime. En contraste,

s.front() = 'x';

Tiene el efecto que esperaría (ya que front devuelve una referencia a un carácter) de cambiar la cadena subyacente, y

s.length() = 4;

También tiene el efecto esperado de generando un error de compilador quejándose de que no se puede asignar a algo que no es un lvalue, porque length devuelve un entero. (Bueno, a size_t de todos modos.)

So... ¿por qué el compilador no se queja de asignar el resultado de una llamada substr? Devuelve un valor de cadena, no una referencia, por lo que no debería ser asignable, ¿verdad? Pero he probado esto en g++ (6.2.1) y clang++ (3.9.0), por lo que no parece ser un error, y tampoco parece ser sensible a la versión de C++ (juzgado 03, 11, 14).

Author: Ken White, 2016-12-12

3 answers

El resultado de substr() es un objeto temporal std::string 's es una copia autónoma de la subcadena, no una vista de la cadena original.

Siendo un objeto std::string, tiene una función de operador de asignación, y su código invoca esa función para modificar el objeto temporal.

Esto es un poco sorprendente modifying modificar un objeto temporal y descartar el resultado generalmente indica un error de lógica, por lo que en general hay dos formas en que las personas intentan mejorar la situación:

  1. Devuelve un objeto const.
  2. Utilice lvalue ref-qualifier en el operador de asignación.

La opción 1 causaría un error de compilación para su código, pero también restringe algunos casos de uso válidos (por ejemplo, move-ing fuera del valor devuelto't no puede moverse fuera de una cadena const).

La opción 2 evita que se utilice el operador de asignación a menos que el lado izquierdo sea un lvalue. Esta es una buena idea en mi humilde opinión, aunque no todos están de acuerdo; ver este hilo para discusión.

En cualquier caso; cuando se agregaron calificadores de referencia en C++11 se propuso volver atrás y cambiar la especificación de todos los contenedores de C++03, pero esta propuesta no fue aceptada (presumiblemente, en caso de que rompiera el código existente).

std::string fue diseñado en la década de 1990 y tomó algunas decisiones de diseño que parecen pobres hoy en retrospectiva, pero estamos atascados con él. Usted tendrá que simplemente entender el problema por std::string en sí, y tal vez evitarlo en su propio clases usando ref-calificadores, o vistas o lo que sea.

 45
Author: M.M,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:00:01

La razón por la que su código se compila es porque es C++legal. Aquí hay un enlace que explica lo que está pasando.

Https://accu.org/index.php/journals/227

Es bastante largo, así que citaré la parte más relevante:

Los rvalues no de clase no son modificables, ni pueden tener tipos calificados para CV (se ignoran las calificaciones del CV). Por el contrario, la clase rvalues son modificables y se pueden utilizar para modificar un objeto a través de su funciones de los miembros. También pueden tener tipos calificados de CV.

Así que la razón por la que no se puede asignar al rvalue devuelto por std::string::length es porque no es una instancia de una clase, y la razón por la que se puede asignar al rvalue devuelto por std::string::substr es porque es una instancia de una clase.

No entiendo por qué el lenguaje se define de esta manera, pero así es como es.

 12
Author: danieltm64,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-12-12 04:46:43

Mira el código:

s.substr(2,3) = "foo";

La llamada a la función substr devuelve una cadena, que es un objeto y un valor temporal. Después de eso se modifica este objeto (en realidad llamando al operador de asignación sobrecargado desde la clase std::string). Este objeto temporal no se guarda de ninguna manera. El compilador simplemente destruye este temporal modificado.

Este código no tiene sentido. Usted puede preguntar, ¿por qué el compilador no está dando una advertencia? La respuesta es que el compilador podría ser mejor. Los compiladores están escritos por la gente, no por los dioses. Desafortunadamente, C++ permite toneladas de varias formas de escribir código sin sentido o código que desencadena un comportamiento indefinido. Este es uno de los aspectos de este lenguaje. Requiere un mejor conocimiento y una mayor atención del programador en comparación con muchos otros idiomas.

Acabo de comprobar con MSVC 2015, el código:

std::string s1 = "abcdef";
s1.substr(1, 2) = "5678";

Compila bien.

 7
Author: Kirill Kobelev,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-12-12 04:20:15