Неопределенное поведение в векторе приведения векторов

19

Почему этот код записывает неопределенное количество, казалось бы, неинициализированных целых чисел?

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


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

Я ожидал, что результат будет 77 777 7777.

Этот код должен быть неопределенным?

GT 77
источник

Ответы:

18

vector<vector<int>>{{77, 777, 7777}}является временным, а затем использование vector<vector<int>>{{77, 777, 7777}}[0]в дальнем расстоянии будет неопределенным поведением.

Сначала вы должны создать переменную, например

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


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

Также, если вы используете Clang 10.0.0, он выдает предупреждение об этом поведении.

предупреждение: объект, поддерживающий указатель, будет уничтожен в конце вектора полного выражения [-Wdangling-gsl]> {{77, 777, 7777}} [0]

Гаурав Дхиман
источник
2
Пожалуйста, используйте using std::vectorвместо using namespace std;того, чтобы предотвратить распространение этой плохой практики.
infinitezero
10

Это потому, что вектор, который вы перебираете, будет уничтожен перед входом в цикл.

Это то, что обычно происходит:

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
}

Проблемы начинаются с первой строки, потому что она оценивается следующим образом:

  1. Сначала создайте вектор векторов с заданными аргументами, и этот вектор станет временным, потому что у него нет имен.

  2. Тогда ссылка на диапазон привязывается к подписанному вектору, который будет действителен только в том случае, если допустим вектор, который его содержит.

  3. Как только точка с запятой достигнута, временный вектор уничтожается, и в своем деструкторе он уничтожает и освобождает любые сохраненные векторы, включая индексированный.

  4. В итоге вы получите ссылку на уничтоженный вектор, который будет повторяться.

Чтобы избежать этой проблемы, есть два решения:

  1. Объявите вектор перед циклом, чтобы он продолжался до тех пор, пока не закончится его область действия, которая включает цикл.

  2. C ++ 20 поставляется с оператором init, который предназначен для решения этих проблем и лучше первого подхода, если вы хотите, чтобы вектор был уничтожен сразу после цикла:

    for (vector<vector<int>> vec{{77, 777, 7777}}; int i : vec[0])
    {
    }
dev65
источник
Это не то, что «обычно» происходит. Такое точное поведение (плюс правильное определение объема и соображения по поводу именования) предусмотрено стандартом при условии соблюдения правила «как будто».
Конрад Рудольф
Я имею в виду времена жизни. Даже если вы пишете один и тот же код от руки, у вас есть только гарантия того, что вы получите желаемое поведение, а компилятор сделает все возможное с оптимизацией
dev65
TIL C ++ 20 декларирующий синтаксис диапазона. Не уверен, быть счастливым или грустным.
Астероиды с крыльями
6
vector<vector<int>>{{77, 777, 7777}}[0]

Я ожидаю, что это болтается.

Хотя определение дальнего радиуса действия гарантирует, что RHS ободочной кишки остается «живым» на время, вы все еще подписываетесь на временную подписку. Сохраняется только результат нижнего индекса, но этот результат является ссылкой, и фактический вектор не может выжить после полного выражения, в котором он объявлен. Это не описывает весь цикл.

Астероиды с крыльями
источник