Nieokreślone zachowanie w wektorze rzutowanych wektorów

19

Dlaczego ten kod zapisuje nieokreśloną liczbę pozornie niezainicjowanych liczb całkowitych?

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


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

Spodziewałem się, że wynik będzie 77 777 7777.

Czy ten kod ma być niezdefiniowany?

GT 77
źródło

Odpowiedzi:

18

vector<vector<int>>{{77, 777, 7777}}jest tymczasowe, a następnie użycie vector<vector<int>>{{77, 777, 7777}}[0]w trybie dystansowym będzie niezdefiniowanym zachowaniem.

Najpierw należy utworzyć zmienną, np

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


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

Również jeśli używasz Clang 10.0.0, ostrzega o tym zachowaniu.

ostrzeżenie: obiekt będący podkładką wskaźnika zostanie zniszczony na końcu wektora z pełnym wyrażeniem [-Wdangling-gsl]> {{77, 777, 7777}} [0]

Gaurav Dhiman
źródło
2
Użyj using std::vectorzamiast using namespace std;, aby zapobiec rozprzestrzenianiu się tej złej praktyki.
infinitezero
10

Jest tak, ponieważ wektor, który iterujesz, zostanie zniszczony przed wejściem do pętli.

Tak zwykle się dzieje:

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
}

Problemy zaczynają się od pierwszej linii, ponieważ są oceniane w następujący sposób:

  1. Najpierw skonstruuj wektor wektorów z podanymi argumentami, a wektor ten stanie się tymczasowy, ponieważ nie ma nazw.

  2. Następnie odwołanie do zakresu jest powiązane z wektorem indeksowanym, który będzie ważny tylko tak długo, jak długo wektor, który go zawiera, jest prawidłowy.

  3. Po osiągnięciu średnika wektor tymczasowy jest niszczony, a w niszczycielu niszczy i zwalnia wszelkie przechowywane wektory, w tym indeksowane.

  4. Kończysz odniesieniem do zniszczonego wektora, który będzie iterowany.

Aby uniknąć tego problemu, istnieją dwa rozwiązania:

  1. Zadeklaruj wektor przed pętlą, aby trwał aż do końca zakresu, który obejmuje pętlę.

  2. C ++ 20 zawiera instrukcję init, która ma na celu rozwiązanie tych problemów i jest lepsza niż pierwsze podejście, jeśli chcesz, aby wektor został natychmiast zniszczony po pętli:

    for (vector<vector<int>> vec{{77, 777, 7777}}; int i : vec[0])
    {
    }
dev65
źródło
Nie dzieje się tak „zwykle”. To dokładne zachowanie (plus właściwy zakres i rozważania na temat nazewnictwa) jest wymagane przez standard, z zastrzeżeniem zasady „tak, jakby”.
Konrad Rudolph
Mam na myśli wcielenia. Nawet jeśli napiszesz ten sam kod ręcznie, masz tylko gwarancję, że uzyskasz pożądane zachowanie, a kompilator zrobi, co może, dzięki optymalizacji
dev65
TIL C ++ 20 deklaracja składni zakresu. Nie jestem pewien, czy być szczęśliwym czy smutnym.
Asteroidy ze skrzydłami
6
vector<vector<int>>{{77, 777, 7777}}[0]

Spodziewam się, że to zwisa.

Chociaż definicja dystansowego zapewnia, że ​​RHS jelita grubego pozostaje „żywy” przez cały czas trwania, nadal zapisujesz się tymczasowo. Zachowywany jest tylko wynik indeksu dolnego, ale ten wynik jest odwołaniem, a rzeczywisty wektor nie może przetrwać po pełnym wyrażeniu, w którym został zadeklarowany. To nie opisuje całej pętli.

Asteroidy Ze Skrzydłami
źródło