Comportamento indefinido no vetor de vetores lançados

19

Por que esse código grava um número indefinido de números inteiros aparentemente não inicializados?

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


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

Eu esperava que a saída fosse 77 777 7777.

Esse código deveria estar indefinido?

GT 77
fonte

Respostas:

18

vector<vector<int>>{{77, 777, 7777}}é temporário e, em seguida, o uso vector<vector<int>>{{77, 777, 7777}}[0]no intervalo será um comportamento indefinido.

Você deve criar uma variável primeiro, 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 << ' ';
}

Além disso, se você usar o Clang 10.0.0, ele avisa sobre esse comportamento.

aviso: o objeto que apóia o ponteiro será destruído no final do vetor [-Wdangling-gsl] de expressão completa> {{77, 777, 7777}} [0]

Gaurav Dhiman
fonte
2
Por favor, use em using std::vectorvez de using namespace std;para impedir que essa má prática se espalhe.
infinitezero
10

Isso ocorre porque o vetor sobre o qual você está repetindo será destruído antes de entrar no loop.

Isto é o que normalmente acontece:

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
}

Os problemas começam na primeira linha porque é avaliado da seguinte maneira:

  1. Primeiro, construa o vetor de vetores com os argumentos fornecidos e esse vetor se torna temporário porque não possui nomes.

  2. Em seguida, a referência de intervalo é vinculada ao vetor subscrito, que só será válido enquanto o vetor que o contém for válido.

  3. Quando o ponto-e-vírgula é atingido, o vetor temporário é destruído e, em seu destruidor, ele destrói e desaloca qualquer vetor armazenado que inclua o vetor subscrito.

  4. Você termina com uma referência a um vetor destruído que será repetido.

Para evitar esse problema, existem duas soluções:

  1. Declare o vetor antes do loop para que ele dure até que seu escopo termine, incluindo o loop.

  2. O C ++ 20 vem com uma instrução init que é fornecida para resolver esses problemas e é melhor que a primeira abordagem se você deseja que o vetor seja destruído imediatamente após o loop:

    for (vector<vector<int>> vec{{77, 777, 7777}}; int i : vec[0])
    {
    }
dev65
fonte
Não é o que "normalmente" acontece. Esse comportamento exato (mais o escopo adequado e as considerações sobre nomeação) é obrigatório pelo padrão, sujeito à regra do tipo "como se".
Konrad Rudolph
Estou me referindo às vidas. Mesmo se você escrever o mesmo código manualmente, terá apenas a garantia de obter o comportamento desejado e o compilador fará o que puder com otimizações
dev65
TIL C ++ 20 na sintaxe do intervalo declarado. Não tenho certeza se será feliz ou triste.
Asteroids With Wings
6
vector<vector<int>>{{77, 777, 7777}}[0]

Espero que isso esteja oscilando.

Embora a definição de um intervalo para garantir que o RHS do cólon permaneça "vivo" durante o período, você ainda está inscrevendo um temporário. Somente o resultado do subscrito é mantido, mas esse resultado é uma referência, e o vetor real não pode sobreviver após a expressão completa em que foi declarado. Isso não descreve o loop inteiro.

Asteróides com asas
fonte