Невизначена поведінка у векторі відведених векторів


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.

Чи повинен цей код бути невизначеним?

Відповіді:


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

TIL C ++ 20 оголошений діапазон - для синтаксису. Не впевнений, бути щасливим чи сумним.
Астероїди з крилами

6
vector<vector<int>>{{77, 777, 7777}}[0]

Я очікую, що це звисає.

Хоча визначення діапазону - запевняє, що РЗС товстої кишки залишається "живим" протягом тривалості, ви все одно підписуєтеся на тимчасову форму. Зберігається лише результат підрозділу, але цей результат є посиланням, і фактичний вектор не може пережити повний вираз, в якому він оголошений. Це не описує весь цикл.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.