﻿# V1098\. The 'emplace' / 'insert' function call contains potentially dangerous move operation\. Moved object can be destroyed even if there is no insertion\.

Анализатор обнаружил потенциально опасное перемещение объекта в ассоциативный контейнер 'std::set' / 'std::map' / 'std::unordered\_map' посредством вызова функции 'emplace' / 'insert' \. Если элемент с указанным ключом уже существует в контейнере, перемещение может привести к преждевременному освобождению ресурсов\.

Рассмотрим следующий пример:

```cpp
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' передается умный указатель на некоторый ресурс и соответствующий ему ключ\. По задумке программиста, умный указатель должен перемещаться в ассоциативный контейнер лишь в том случае, если ранее не было вставки с тем же ключом\. Если вставки не произошло, то далее с ресурсом будет произведена некоторая работа через умный указатель\.

Однако такой код содержит две проблемы:

* Если вставки не произошло, ресурс из указателя 'ptr' всё равно может быть перемещён\. Это приведет к тому, что он будет освобождён раньше времени\.
* Указатель 'ptr' может стать нулевым, а его разыменование приведёт к [неопределенному поведению](https://pvs-studio.ru/ru/blog/terms/0066/)\.

Рассмотрим возможные способы исправления проблем\.

## Функция 'try\_emplace' для 'std::map' / 'std::unordered\_map'

Начиная со стандарта С\+\+17, для контейнеров 'std::map' и 'std::unordered\_map' была добавлена функция 'try\_emplace'\. Она гарантирует, что если элемент с указанным ключом уже существует, то аргументы функции не будут скопированы или перемещены\. Поэтому для контейнеров 'std::map' и 'std::unordered\_map' рекомендуется использовать именно эту функцию вместо 'emplace' и 'insert'\.

Исправленный код:

```cpp
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;
}
```

## Функции 'lower\_bound' и 'emplace\_hint' для 'std::set' / 'std::map'

Если функция 'try\_emplace' недоступна, то для ассоциативных упорядоченных контейнеров \('std::set', 'std::map'\) поиск и вставку можно разделить на две операции:

1. функция 'lower\_bound' позволит либо найти элемент по заданному ключу, либо позицию вставки для нового элемента;
1. функция 'emplace\_hint' позволит эффективно вставить элемент\.

Рассмотрим предыдущий пример, заменив контейнер на 'std::map' и воспользовавшись функциями 'lower\_bound' и 'emplace\_hint':

```cpp
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;
}
```

## Примечание N1

Анализатор может выдавать срабатывания на похожий код:

```cpp
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'\. Поэтому рекомендуется оптимизировать код одним из описанных ранее способов\.

## Примечание N2

Диагностика имеет два уровня достоверности\. Первый уровень выдаётся для move\-only объектов, т\.е\. когда пользовательский тип не имеет конструкторов и операторов копирования\. Это значит, при неудачной вставке ресурс может быть освобождён раньше времени\. Например, это актуально для типов 'std::unique\_ptr' и 'std::unique\_lock'\. Иначе будет выдан второй уровень достоверности\.

Диагностика не работает с типами, у которых отсутствует конструктор перемещения, т\.к\. в таком случае объекты будут копироваться\.