Comportement indéfini dans le vecteur de vecteurs cast

19

Pourquoi ce code écrit-il un nombre indéfini d'entiers apparemment non initialisés?

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


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

Je m'attendais à ce que la sortie soit 77 777 7777.

Ce code est-il censé être indéfini?

GT 77
la source

Réponses:

18

vector<vector<int>>{{77, 777, 7777}}est un vector<vector<int>>{{77, 777, 7777}}[0]comportement temporaire et son utilisation à distance sera un comportement indéfini.

Vous devez d'abord créer une variable, comme

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


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

De plus, si vous utilisez Clang 10.0.0, il avertit de ce comportement.

avertissement: les objets qui soutiennent le pointeur seront détruits à la fin du vecteur d'expression complète [-Wdangling-gsl]> {{77, 777, 7777}} [0]

Gaurav Dhiman
la source
2
Veuillez utiliser à la using std::vectorplace de using namespace std;afin d'éviter que cette mauvaise pratique ne se propage.
infinitezero
10

En effet, le vecteur que vous parcourez sera détruit avant d'entrer dans la boucle.

C'est ce qui se produit généralement:

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
}

Les problèmes commencent à la première ligne car ils sont évalués comme suit:

  1. Construisez d'abord le vecteur de vecteurs avec les arguments donnés et ce vecteur devient temporaire car il n'a pas de nom.

  2. Ensuite, la référence de plage est liée au vecteur en indice qui ne sera valide que tant que le vecteur qui la contient est valide.

  3. Une fois le point-virgule atteint, le vecteur temporaire est détruit et dans son destructeur, il détruira et désallouera tous les vecteurs stockés, y compris le vecteur en indice.

  4. Vous vous retrouvez avec une référence à un vecteur détruit qui sera itéré.

Pour éviter ce problème, il existe deux solutions:

  1. Déclarez le vecteur avant la boucle pour qu'il dure jusqu'à la fin de sa portée, ce qui inclut la boucle.

  2. C ++ 20 est livré avec une instruction init qui est fournie pour résoudre ces problèmes et est meilleure que la première approche si vous voulez que le vecteur soit immédiatement détruit après la boucle:

    for (vector<vector<int>> vec{{77, 777, 7777}}; int i : vec[0])
    {
    }
dev65
la source
Ce n'est pas ce qui se passe «typiquement». Ce comportement exact (plus la portée et les considérations appropriées sur la dénomination) est imposé par la norme, sous réserve de la règle du cas comme si.
Konrad Rudolph
Je parle des vies. Même si vous écrivez le même code à la main, vous avez seulement la garantie d'obtenir le comportement souhaité et le compilateur fera ce qu'il peut avec des optimisations
dev65
TIL C ++ 20 la syntaxe déclarante de l'intervalle. Je ne sais pas s'il faut être heureux ou triste.
Asteroids With Wings
6
vector<vector<int>>{{77, 777, 7777}}[0]

Je m'attends à ce que cela pende.

Bien que la définition d'un à distance assure que la RHS du côlon reste "vivante" pendant toute la durée, vous êtes toujours en train de souscrire un temporaire. Seul le résultat de l'indice est conservé, mais ce résultat est une référence et le vecteur réel ne peut pas survivre au-delà de l' expression complète dans laquelle il est déclaré. Cela ne décrit pas toute la boucle.

Astéroïdes avec des ailes
la source