c++ - dev - ¿Por qué el uso de un objeto temporal en el rango para el inicializador resulta en un bloqueo?
c++17 (2)
La línea de inicialización de rango de un bucle
for(:)
no extiende la vida útil de otra cosa que no sea la temporal
final
(si existe).
Cualquier otro temporal se descarta antes de la ejecución del bucle
for(:)
.
Ahora, no te desesperes; Hay una solución fácil a este problema. Pero primero un paseo por lo que está yendo mal.
El código
for(auto x:exp){ /* code */ }
expande básicamente a:
{
auto&& __range=exp;
auto __it=std::begin(__range);
auto __end=std::end(__range);
for(; __it!=__end;++__it){
auto x=*__it;
/* code */
}
}
(Con mentiras modestas en las líneas
__it
y
__end
, y todas las variables que comienzan con
__
no tienen nombre visible. También estoy mostrando la versión C ++ 17, porque creo en un mundo mejor, y las diferencias no importan aquí).
Su
exp
crea un objeto temporal y luego le devuelve una referencia.
El temporal muere después de esa línea, por lo que tiene una referencia colgante en el resto del código.
Arreglarlo es relativamente fácil. Arreglarlo:
std::string const& func() const& // notice &
{
return m.find("key")->second;
}
std::string func() && // notice &&
{
return std::move(m.find("key")->second);
}
haga sobrevaloraciones de valor y devuelva valores movidos por valor al consumir temporarios en lugar de devolver referencias a ellos.
Entonces la
auto&& __range=exp;
la línea hace referencia a la extensión de por vida en la
string
devuelta por valor, y no más referencias colgantes.
Como regla general, nunca devuelva un rango por referencia a un parámetro que podría ser un valor r.
Apéndice: ¿Esperar,
&&
y
const&
after métodos?
rvalue referencias a
*this
?
C ++ 11 agregó referencias de valor r.
Pero el parámetro
this
o self para funciones es especial.
Para seleccionar qué sobrecarga de un método en función del valor / valor del objeto que se invoca, puede usar
&
o
&&
después del final del método.
Esto funciona de manera muy similar al tipo de un parámetro para una función.
&&
después del método establece que el método debe llamarse solo en valores no constantes;
const&
significa que debería llamarse a valores constantes.
Las cosas que no coinciden exactamente siguen las reglas habituales de precidencia.
Cuando tenga un método que devuelva una referencia a un objeto, asegúrese de capturar temporarios con una sobrecarga de
&&
y no devuelva una referencia en esos casos (devuelva un valor) o
=delete
el método.
¿Por qué el siguiente código falla tanto en Visual Studio como en GCC?
Para que se bloquee, se requiere el bucle basado en rango, std :: map, std :: string y tomar una referencia a la cadena. Si elimino alguno de ellos, funcionará.
#include <iostream>
#include <string>
#include <map>
using namespace std;
struct S
{
map<string, string> m;
S()
{
m["key"] = "b";
}
const string &func() const
{
return m.find("key")->second;
}
};
int main()
{
for (char c : S().func())
cout << c;
return 0;
}
Enlace de Ideone: http://ideone.com/IBmhDH
S().func()
Esto construye un objeto temporal e invoca un método que devuelve una referencia a un
std::string
que es propiedad (indirectamente) del objeto temporal (el
std::string
está en el contenedor que es parte del objeto temporal).
Después de obtener la referencia, el objeto temporal se destruye.
Esto también destruye el
std::string
que era propiedad (indirectamente) del objeto temporal.
Después de ese punto, cualquier uso posterior del objeto referenciado se convierte en un comportamiento indefinido. Tal como iterar sobre sus contenidos.
Esta es una trampa muy común cuando se trata de usar la iteración de rango. La tuya también es culpable de haber sido tropezada por esto.