❌ Проблемный код
У меня был
std::list<std::unique_ptr<Item>>, и я хотел переместить завершённые элементы в другой список. Написал, казалось бы, логичный код:auto it = std::remove_if(active.begin(), active.end(), [](auto& item) {
if (item->is_completed()) {
item->reset();
return true;
}
return false;
});
// Перенос в другой список
stopped_submits_.splice(stopped.begin(), active, it, active.end());
И... неожиданно обнаружил нулевые указатели в списке!
🔍 Что пошло не так?
Ошибка №1: Нарушение контракта предиката
Сигнатура
std::remove_if требует, чтобы предикат не модифицировал элементы:template< class ForwardIt, class UnaryPred >
ForwardIt remove_if(ForwardIt first, ForwardIt last, UnaryPred p );
Из документации:
>The expression p(v) must be convertible to bool for every argument v of type (possibly const) VT, where VT is the value type of ForwardIt, regardless of value category, and must not modify v.
Мой
item.reset() явно нарушает это требование.Ошибка №2: Неопределённое состояние после
remove_ifПосле работы
std::remove_if элементы в диапазоне [result, last) могут находятся в неопределённом состоянии. Для std::unique_ptr это означает, что они могут стать нулевыми в результате перемещения!Из документации:
> Each element of [result, last) has a valid but unspecified state, because move assignment can eliminate elements by moving from elements that were originally in that range.
📚 Выводы
1. Используйте
std::remove/remove_if в рамках идиомы remove-erase3. Читайте документацию и стандарт, в частности cppreference
#cpp #stl #cplusplus #bugs