versiones dev compiler c++ c++11

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.