Странное поведение с std::vector в С++

Почему это не работает?

Это базовая система инвентаризации. inventory — это std::vector<Item>, а Item — это структура с int quantity и std::string name.

struct Item
{
    int quantity;
    std::string name;
};

void AddItemToInventory(std::string itemName)
{
    Item i;
    i.name = itemName;
    if (inventory.empty())
    {
        i.quantity = 1;
        inventory.push_back(i);
        std::cout << "EMPTY\n";
    }
    else
    {
        for (auto& item : inventory)
        {
            if (i.name == item.name)
            {
                i.quantity = item.quantity + 1;
            }
            else
            {
                i.quantity = 1;
            }
            inventory.push_back(i);
        }
    }
}

Я ожидал, что это сработает. Когда он пуст, добавьте один к inventory из name, а когда он не пуст, просто добавьте еще один. Если он существует, просто добавьте 1 к quantity.

Изменение контейнера во время итерации по нему с помощью цикла с диапазоном for приводит к UB. В любом случае, похоже, вам нужен std::map, а не std::vector (но отладка проблемы с помощью vector, вероятно, имеет обучающую ценность).

HolyBlackCat 16.04.2024 19:52

Вы вставляете новый элемент на каждой итерации цикла.

Damien 16.04.2024 19:56
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
2
138
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Вам не нужна первая проверка empty(), цикл прекрасно обработает пустую vector.

Ваш цикл неправильный, потому что он помещает новый Item в vector на каждой итерации (что является неопределенным поведением для цикла range-for).

Вообще не вызывайте push_back() внутри цикла. Сначала пройдитесь по vector в поисках нужного Item. Если он найден, вы можете обновить его и прекратить цикл. Если он не найден, добавьте его после завершения цикла.

Попробуй это:

void AddItemToInventory(std::string itemName)
{
    for (auto& item : inventory)
    {
        if (item.name == itemName)
        {
            item.quantity += 1;
            std::cout << "UPDATED\n";
            return;
        }
    }

    Item newItem;
    newItem.name = itemName;
    newItem.quantity = 1;

    inventory.push_back(newItem);

    std::cout << "ADDED\n";
}

ОБНОВЛЕНИЕ: При этом рассмотрите возможность полного удаления ручного цикла и вместо этого используйте std::find_if (), например:

void AddItemToInventory(std::string itemName)
{
    auto found = std::find_if (
        inventory.begin(), inventory.end(), 
        [&](const Item &item){ return item.name == itemName; }
    );

    if (found != inventory.end())
    {
        found->quantity += 1;
        std::cout << "UPDATED\n";
    }
    else 
    {
        Item newItem;
        newItem.name = itemName;
        newItem.quantity = 1;

        inventory.push_back(newItem);

        std::cout << "ADDED\n";
    }
}

Возможно, стоит процитировать документацию по push_back. Например, на странице cppreference.com в push_back говорится: Если после операции новый размер() превышает старый емкость(), происходит перераспределение, и в этом случае все итераторы (включая итератор end()) и все ссылки на элементы становятся недействительными. В противном случае только итератор end() будет признан недействительным.

Chris 16.04.2024 20:25

@wohlstad Диапазон, основанный на цикле for, — это не то же самое, что необработанный цикл for, ИМХО. Для таких простых вещей я предпочитаю использовать их, поскольку их легче читать.

NathanOliver 16.04.2024 20:32

Проблема в том, что вы не можете добавить элемент в контейнер (как вы это делаете с push_back) во время итерации по нему на основе диапазона. На самом деле вам следует добавить его после цикла (и только если элемент не найден).

Однако в этом случае вы можете вообще избежать цикла и использовать std::find_if из заголовка <algorithm>.

Он выполнит поиск в контейнере и вернет итератор элементу (если он найден) или итератор end (если нет).

Лямбда используется для проверки того, соответствует ли имя элемента имени элемента в контейнере.

Это показано ниже:

#include <algorithm>   // required for std::find_if

void AddItemToInventory(std::string const & itemName)
{
    auto it = std::find_if (inventory.begin(), 
                           inventory.end(), 
                           [&itemName](Item const& item) { return item.name == itemName; });
    if (it == inventory.end())
    {
        Item i;
        i.name = itemName;
        i.quantity = 1;
        inventory.push_back(i);
        std::cout << "Added\n";
    }
    else
    {
        it->quantity += 1;
        std::cout << "Updated\n";
    }
}

Обратите внимание, что я изменил itemName на const&, чтобы избежать ненужной копии при передаче AddItemToInventory.

Демо - Godbolt .

Другие вопросы по теме