Comportamento indefinito nel vettore del cast di vettori


19

Perché questo codice scrive un numero indefinito di numeri interi apparentemente non inizializzati?

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


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

Mi aspettavo che l'output fosse 77 777 7777.

Questo codice dovrebbe essere indefinito?

Risposte:


18

vector<vector<int>>{{77, 777, 7777}}è temporaneo e quindi l'utilizzo vector<vector<int>>{{77, 777, 7777}}[0]in rangeed per sarà un comportamento indefinito.

Dovresti prima creare una variabile, ad esempio

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


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

Inoltre, se si utilizza Clang 10.0.0, viene visualizzato un avviso su questo comportamento.

avvertimento: l'oggetto che sostiene il puntatore verrà distrutto alla fine del vettore [-Wdangling-gsl] a piena espressione> {{77, 777, 7777}} [0]


2
Si prega di utilizzare using std::vectorinvece di using namespace std;per evitare che questa cattiva pratica si diffonda.
infinitezero

10

Questo perché il vettore su cui stai ripetendo verrà distrutto prima di entrare nel ciclo.

Questo è ciò che accade in genere:

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
}

I problemi iniziano alla prima riga perché viene valutata come segue:

  1. Innanzitutto costruisci il vettore dei vettori con gli argomenti forniti e quel vettore diventa temporaneo perché non ha nomi.

  2. Quindi il riferimento dell'intervallo viene associato al vettore sottoscritto che sarà valido solo finché il vettore che lo contiene è valido.

  3. Una volta raggiunto il punto e virgola, il vettore temporaneo viene distrutto e nel suo distruttore distruggerà e deallocherà tutti i vettori memorizzati che include quello sottoscritto.

  4. Si finisce con un riferimento a un vettore distrutto che verrà ripetuto.

Per evitare questo problema ci sono due soluzioni:

  1. Dichiarare il vettore prima del ciclo in modo che duri fino alla fine del suo ambito che include il ciclo.

  2. C ++ 20 viene fornito con un'istruzione init che viene fornita per risolvere questi problemi ed è migliore del primo approccio se si desidera che il vettore venga distrutto immediatamente dopo il ciclo:

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

Questo non è ciò che "tipicamente" accade. Questo comportamento esatto (oltre all'adeguato ambito e considerazioni sulla denominazione) è richiesto dallo standard, soggetto alla regola as-if.
Konrad Rudolph,

Mi riferisco alle vite. Anche se scrivi lo stesso codice a mano hai solo la garanzia di ottenere il comportamento desiderato e il compilatore farà ciò che può con le ottimizzazioni
dev65

TIL C ++ 20 l'intervallo dichiarante per la sintassi. Non sono sicuro di essere felice o triste.
Asteroidi con le ali

6
vector<vector<int>>{{77, 777, 7777}}[0]

Mi aspetto che sia penzolante.

Sebbene la definizione di un intervallo assicuri che l'RHS del colon rimanga "vivo" per la durata, stai ancora sottoscrivendo un temporaneo. Viene conservato solo il risultato del pedice, ma quel risultato è un riferimento e il vettore reale non può sopravvivere oltre l' espressione completa in cui è dichiarato. Questo non descrive l'intero ciclo.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.