Entiendo que en C ++ 11, cuando devuelve una variable local de una función por valor, el compilador puede tratar esa variable como una referencia de valor r y 'moverla' fuera de la función para devolverla (si RVO / NRVO no sucede en su lugar, por supuesto).
Mi pregunta es, ¿no puede esto romper el código existente?
Considere el siguiente código:
#include <iostream>
#include <string>
struct bar
{
bar(const std::string& str) : _str(str) {}
bar(const bar&) = delete;
bar(bar&& other) : _str(std::move(other._str)) {other._str = "Stolen";}
void print() {std::cout << _str << std::endl;}
std::string _str;
};
struct foo
{
foo(bar& b) : _b(b) {}
~foo() {_b.print();}
bar& _b;
};
bar foobar()
{
bar b("Hello, World!");
foo f(b);
return std::move(b);
}
int main()
{
foobar();
return EXIT_SUCCESS;
}
Pensé que sería posible que un destructor de un objeto local haga referencia al objeto que se mueve implícitamente y, por lo tanto, vea inesperadamente un objeto 'vacío'. He intentado poner a prueba esta (ver http://ideone.com/ZURoeT ), pero me dio el resultado 'correcto' sin la explícita std::move
en foobar()
. Supongo que se debió a NRVO, pero no intenté reorganizar el código para deshabilitarlo.
¿Estoy en lo cierto en que esta transformación (que causa un movimiento fuera de la función) ocurre implícitamente y podría romper el código existente?
ACTUALIZACIÓN Aquí hay un ejemplo que ilustra de lo que estoy hablando. Los siguientes dos enlaces son para el mismo código. http://ideone.com/4GFIRu - C ++ 03 http://ideone.com/FcL2Xj - C ++ 11
Si nos fijamos en la salida, es diferente.
Entonces, supongo que esta pregunta ahora es: ¿se consideró esto al agregar un movimiento implícito al estándar, y se decidió que estaba bien agregar este cambio importante ya que este tipo de código es bastante raro? También me pregunto si algún compilador advertirá en casos como este ...
Respuestas:
Scott Meyers publicó en comp.lang.c ++ (agosto de 2010) sobre un problema en el que la generación implícita de constructores de movimientos podría romper invariantes de clase C ++ 03:
Aquí el problema es que en C ++ 03,
X
tenía un invariante que suv
miembro siempre tenía 5 elementos.X::~X()
contaba con ese invariante, pero el constructor de movimiento recién introducido se movióv
, estableciendo así su longitud en cero.Esto está relacionado con su ejemplo, ya que el invariante roto solo se detecta en el
X
destructor del s (como usted dice, es posible que un destructor de un objeto local haga referencia al objeto que se mueve implícitamente y, por lo tanto, ve inesperadamente un objeto vacío ).C ++ 11 intenta lograr un equilibrio entre romper parte del código existente y proporcionar optimizaciones útiles basadas en constructores de movimientos.
El comité decidió inicialmente que los compiladores de movimientos y los operadores de asignación de movimientos deberían ser generados por el compilador cuando el usuario no los proporciona.
Luego decidió que esto era realmente motivo de alarma y restringió la generación automática de constructores de movimiento y operadores de asignación de movimiento de tal manera que es mucho menos probable, aunque no imposible, que se rompa el código existente (por ejemplo, destructor definido explícitamente).
Es tentador pensar que evitar la generación de constructores de movimiento implícito cuando está presente un destructor definido por el usuario es suficiente, pero no es cierto ( N3153 - El movimiento implícito debe ir para más detalles).
En N3174 - Moverse o no moverse Stroupstrup dice:
fuente