Анализатор обнаружил потенциально опасное перемещение объекта в ассоциативный контейнер 'std::set' / 'std::map' / 'std::unordered_map' посредством вызова функции 'emplace' / 'insert' . Если элемент с указанным ключом уже существует в контейнере, перемещение может привести к преждевременному освобождению ресурсов.
Рассмотрим следующий пример:
using pointer_type = std::unique_ptr<void, void (*)(void *)>;
std::unordered_map<uintmax_t, pointer_type> Cont;
// Unique pointer should be moved only if
// there is no element in the container by the specified key
bool add_entry(uintmax_t key, pointer_type &&ptr)
{
auto [it, inserted] = Cont.emplace(key, std::move(ptr));
if (!inserted)
{
// dereferencing the potentially null pointer 'ptr' here
}
return inserted;
}
В примере в функцию 'add_entry' передается умный указатель на некоторый ресурс и соответствующий ему ключ. По задумке программиста, умный указатель должен перемещаться в ассоциативный контейнер лишь в том случае, если ранее не было вставки с тем же ключом. Если вставки не произошло, то далее с ресурсом будет произведена некоторая работа через умный указатель.
Однако такой код содержит две проблемы:
Рассмотрим возможные способы исправления проблем.
Начиная со стандарта С++17, для контейнеров 'std::map' и 'std::unordered_map' была добавлена функция 'try_emplace'. Она гарантирует, что если элемент с указанным ключом уже существует, то аргументы функции не будут скопированы или перемещены. Поэтому для контейнеров 'std::map' и 'std::unordered_map' рекомендуется использовать именно эту функцию вместо 'emplace' и 'insert'.
Исправленный код:
using pointer_type = std::unique_ptr<void, void (*)(void *)>;
std::unordered_map<uintmax_t, pointer_type> Cont;
bool add_entry(uintmax_t key, pointer_type &&ptr)
{
auto [it, inserted] = Cont.try_emplace(key, std::move(ptr));
if (!inserted)
{
// dereferencing the 'ptr' here
// 'ptr' is guaranteed to be non-null
}
return inserted;
}
Если функция 'try_emplace' недоступна, то для ассоциативных упорядоченных контейнеров ('std::set', 'std::map') поиск и вставку можно разделить на две операции:
Рассмотрим предыдущий пример, заменив контейнер на 'std::map' и воспользовавшись функциями 'lower_bound' и 'emplace_hint':
using pointer_type = std::unique_ptr<void, void (*)(void *)>;
std::map<uintmax_t, pointer_type> Cont;
// Unique pointer should be moved only if
// there is no element in the container by the specified key
bool add_entry(uintmax_t key, pointer_type &&ptr)
{
bool inserted;
auto it = Cont.lower_bound(key);
if (it != Cont.end() && key == it->first)
{
// key exists
inserted = false;
}
else
{
// key doesn't exist
it = Cont.emplace_hint(it, key, std::move(ptr));
inserted = true;
}
if (!inserted)
{
// dereferencing the 'ptr' here
// 'ptr' is guaranteed to be non-null
}
return inserted;
}
Анализатор может выдавать срабатывания на похожий код:
using pointer_type = std::unique_ptr<void, void (*)(void *)>;
std::map<uintmax_t, pointer_type> Cont;
// Unique pointer should be moved only if
// there is no element in the container by the specified key
bool add_entry(uintmax_t key, pointer_type &&ptr)
{
bool inserted;
auto it = Cont.find(key);
if (it == Cont.end())
{
std::tie(it, inserted) = Cont.emplace(key, std::move(ptr)); // <=
}
else
{
inserted = false;
}
if (!inserted)
{
// dereferencing the 'ptr' here
// 'ptr' is guaranteed to be non-null
}
return inserted;
}
В примере нет ошибки: если элемента по заданному ключу не существует, то гарантированно произойдет вставка. Однако код не оптимален: сначала ведется поиск элемента по ключу, затем поиск повторяется для нахождения позиции вставки внутри функции 'emplace'. Поэтому рекомендуется оптимизировать код одним из описанных ранее способов.
Диагностика имеет два уровня достоверности. Первый уровень выдаётся для move-only объектов, т.е. когда пользовательский тип не имеет конструкторов и операторов копирования. Это значит, при неудачной вставке ресурс может быть освобождён раньше времени. Например, это актуально для типов 'std::unique_ptr' и 'std::unique_lock'. Иначе будет выдан второй уровень достоверности.
Диагностика не работает с типами, у которых отсутствует конструктор перемещения, т.к. в таком случае объекты будут копироваться.
Данная диагностика классифицируется как: