Undefiniertes Verhalten im Vektor der gegossenen Vektoren

19

Warum schreibt dieser Code eine undefinierte Anzahl scheinbar nicht initialisierter Ganzzahlen?

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


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

Ich habe erwartet, dass die Ausgabe sein wird 77 777 7777.

Soll dieser Code undefiniert sein?

GT 77
quelle

Antworten:

18

vector<vector<int>>{{77, 777, 7777}}ist ein temporäres und dann vector<vector<int>>{{77, 777, 7777}}[0]in Fernkampf verwendetes wird ein undefiniertes Verhalten sein.

Sie sollten zuerst eine Variable erstellen, wie z

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


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

Auch wenn Sie Clang 10.0.0 verwenden, wird vor diesem Verhalten gewarnt.

Warnung: Objekt, das den Zeiger unterstützt, wird am Ende des Vektors [-Wdangling-gsl] mit vollem Ausdruck> {{77, 777, 7777}} [0] zerstört.

Gaurav Dhiman
quelle
2
Bitte verwenden Sie using std::vectorstatt, using namespace std;um zu verhindern, dass sich diese schlechte Praxis verbreitet.
Infinitezero
10

Dies liegt daran, dass der Vektor, über den Sie iterieren, vor dem Eintritt in die Schleife zerstört wird.

Dies ist, was normalerweise passiert:

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
}

Die Probleme beginnen in der ersten Zeile, da sie wie folgt bewertet werden:

  1. Konstruieren Sie zunächst den Vektorvektor mit den angegebenen Argumenten, und dieser Vektor wird temporär, da er keine Namen hat.

  2. Dann wird die Bereichsreferenz an den tiefgestellten Vektor gebunden, der nur gültig ist, solange der Vektor, der ihn enthält, gültig ist.

  3. Sobald das Semikolon erreicht ist, wird der temporäre Vektor zerstört und in seinem Destruktor werden alle gespeicherten Vektoren, einschließlich des tiefgestellten, zerstört und freigegeben.

  4. Am Ende erhalten Sie einen Verweis auf einen zerstörten Vektor, der wiederholt wird.

Um dieses Problem zu vermeiden, gibt es zwei Lösungen:

  1. Deklarieren Sie den Vektor vor der Schleife, damit er so lange dauert, bis sein Gültigkeitsbereich endet, der die Schleife enthält.

  2. C ++ 20 enthält eine init-Anweisung, die zur Lösung dieser Probleme bereitgestellt wird und besser als der erste Ansatz ist, wenn der Vektor sofort nach der Schleife zerstört werden soll:

    for (vector<vector<int>> vec{{77, 777, 7777}}; int i : vec[0])
    {
    }
dev65
quelle
Dies ist nicht das, was „normalerweise“ passiert. Dieses genaue Verhalten (plus angemessener Geltungsbereich und Überlegungen zur Benennung) wird vom Standard vorgeschrieben, vorbehaltlich der Als-ob-Regel.
Konrad Rudolph
Ich beziehe mich auf die Lebenszeiten. Selbst wenn Sie denselben Code von Hand schreiben, haben Sie nur die Garantie, dass Sie das gewünschte Verhalten erhalten, und der Compiler wird mit Optimierungen tun, was er kann
dev65
TIL C ++ 20 die deklarierende Range-for-Syntax. Ich bin mir nicht sicher, ob ich glücklich oder traurig sein soll.
Asteroiden mit Flügeln
6
vector<vector<int>>{{77, 777, 7777}}[0]

Ich gehe davon aus, dass dies baumelt.

Obwohl die Definition eines Fernkampfs sicherstellt, dass die RHS des Doppelpunkts für die Dauer "am Leben" bleibt, abonnieren Sie immer noch eine temporäre. Es wird nur das Ergebnis des Index beibehalten, aber dieses Ergebnis ist eine Referenz, und der tatsächliche Vektor kann nicht über den vollständigen Ausdruck hinaus überleben, in dem er deklariert ist. Das beschreibt nicht die ganze Schleife.

Asteroiden mit Flügeln
quelle