Comportamiento indefinido en el vector de vectores emitidos

19

¿Por qué este código escribe un número indefinido de enteros aparentemente sin inicializar?

#include <iostream>
#include <vector>
using namespace std;


int main()
{
    for (int i : vector<vector<int>>{{77, 777, 7777}}[0])
        cout << i << ' ';
}

Esperaba que la salida fuera 77 777 7777.

¿Se supone que este código no está definido?

GT 77
fuente

Respuestas:

18

vector<vector<int>>{{77, 777, 7777}}es temporal y luego usarlo vector<vector<int>>{{77, 777, 7777}}[0]en rango será un comportamiento indefinido.

Primero debe crear una variable, como

#include <iostream>
#include <vector>
using namespace std;


int main()
{
    auto v = vector<vector<int>>{{77, 777, 7777}};
    for(int i: v[0])
        cout << i << ' ';
}

Además, si usa Clang 10.0.0, le avisa sobre este comportamiento.

advertencia: el objeto que respalda el puntero se destruirá al final del vector de expresión completa [-Wdangling-gsl]> {{77, 777, 7777}} [0]

Gaurav Dhiman
fuente
2
Úselo en using std::vectorlugar de using namespace std;para evitar que se propague esta mala práctica.
infinitezero
10

Esto se debe a que el vector sobre el que está iterando se destruirá antes de ingresar al bucle.

Esto es lo que suele suceder:

auto&& range = vector<vector<int>>{{77, 777, 7777}}[0];
auto&& first = std::begin(range);
auto&& last = std::end(range);
for(; first != last; ++first)
{
    int i = *first;
    // the rest of the loop
}

Los problemas comienzan en la primera línea porque se evalúa de la siguiente manera:

  1. Primero, construya el vector de vectores con los argumentos dados y ese vector se convierte en temporal porque no tiene nombres.

  2. Luego, la referencia de rango está vinculada al vector suscrito, que solo será válido mientras el vector que lo contiene sea válido.

  3. Una vez que se alcanza el punto y coma, el vector temporal se destruye y en su destructor destruirá y desasignará cualquier vector almacenado que incluya el subíndice.

  4. Termina con una referencia a un vector destruido que se repetirá.

Para evitar este problema hay dos soluciones:

  1. Declare el vector antes del ciclo para que dure hasta que termine su alcance, que incluye el ciclo.

  2. C ++ 20 viene con una instrucción init que se proporciona para resolver estos problemas y es mejor que el primer enfoque si desea que el vector se destruya inmediatamente después del ciclo:

    for (vector<vector<int>> vec{{77, 777, 7777}}; int i : vec[0])
    {
    }
dev65
fuente
Esto no es lo que sucede "típicamente". Este comportamiento exacto (más el alcance adecuado y las consideraciones sobre la denominación) es obligatorio según el estándar, sujeto a la regla como si.
Konrad Rudolph
Me refiero a las vidas. Incluso si escribe el mismo código a mano, solo tiene la garantía de obtener el comportamiento deseado y el compilador hará lo que pueda con las optimizaciones
dev65
TIL C ++ 20 la sintaxis de rango de declaración. No estoy seguro de si ser feliz o triste.
Asteroides con alas
6
vector<vector<int>>{{77, 777, 7777}}[0]

Espero que esto esté colgando.

Aunque la definición de un rango asegura que el RHS del colon permanece "vivo" durante el tiempo, todavía está suscribiendo un temporal. Solo se mantiene el resultado del subíndice, pero ese resultado es una referencia, y el vector real no puede sobrevivir más allá de la expresión completa en la que se declara. Eso no describe todo el ciclo.

Asteroides con alas
fuente