У меня есть простая база данных, состоящая из объектов со строками, содержащими время unix в качестве ключей, и строками, содержащими инструкции в качестве значений.
Я хочу выполнить итерацию по базе данных и стереть любой объект, ключ которого меньше текущего времени (поэтому стереть объекты с датами до текущей даты)
for (auto it = m_jsonData.begin(); it != m_jsonData.end(); it++) {
if (std::stoi(it.key()) <= (std::time(NULL))) {
std::cout << "command is behind schedule, removing\n";
m_jsonData.erase(it);
} else {
/*
*/
}
}
этот код работает нормально, пока не вызывается m_jsonData.erase(it);
. когда это происходит, на следующей итерации std::stoi(it.key())
вызывает segfault, немного поигравшись с ним, я пришел к выводу, что он каким-то образом теряет представление о том, что он на самом деле повторяет. Верен ли мой вывод? Если нет, то что? И как мне это исправить?
@πάνταῥεῖ m_jsonData — это тип данных basic_json, как описано здесь связь (внутри представляет собой некоторую форму ассоциативного контейнера). Насколько я понимаю, удаление элементов в диапазоне итераций — это определенное поведение, как показано в примерах здесь связь
Обычно вы получаете следующий действительный итератор в результате erase()
, проблема заключается в it++
на следующей итерации цикла.
Это очень сильно нормально для изменения операций контейнера, чтобы сделать итераторы недействительными. Это одна из первых вещей, которую вы должны проверить.
Документация для nlohnmann::json::erase()
:
Notes
- Invalidates iterators and references at or after the point of the erase, including the end() iterator.
- References and iterators to the erased elements are invalidated. Other references and iterators are not affected.
Это означает, что после этой строки:
m_jsonData.erase(it);
итератор it
нельзя использовать ни для чего в том числе, увеличивая его до следующего элемента. Это недействительно.
К счастью, в документации также указано, что возвращается преемник удаленного элемента, поэтому вы можете просто написать
for (auto it = m_jsonData.begin(); it != m_jsonData.end(); ) {
if (std::stoi(it.key()) <= (std::time(NULL))) {
it = m_jsonData.erase(it);
} else {
++it;
}
}
Обратите внимание: когда я говорю, что это совершенно нормально, это происходит потому, что стандартные контейнеры часто имеют похожее поведение. См. документацию для примеров, но это то, о чем должен знать каждый:
Именно по этой причине std::erase
был добавлен в C++20, а ранее std::remove_if
предоставлялся для поддержки идиомы erase(remove_if (...), end)
вместо написания хрупких мутирующих циклов.
Да, это работает, большое спасибо, оказывается, обучение пониманию прочитанного — это процесс длиною в жизнь!
Если упоминалось std::vector::erase
, то стоит отметить, что C++20 представил станд:: стереть, который помогает удалять элементы из вектора.
Хороший вопрос, я добавлю примечание.
Не зная точного типа
m_jsonData
, я подозреваю, чтоerase(it)
сделает недействительными итераторы.