>
>
>
V837. The 'emplace' / 'insert' function…


V837. The 'emplace' / 'insert' function does not guarantee that arguments will not be copied or moved if there is no insertion. Consider using the 'try_emplace' function.

Анализатор обнаружил использование функции 'emplace' / 'insert' ассоциативного контейнера стандартной библиотеки ('std::map', 'std::unordered_map'), у которого существует функция 'try_emplace'. Функция 'emplace' / 'insert' может привести к копированию или перемещению аргументов, даже если вставки не произойдёт (элемент с указанным ключом уже присутствует в контейнере). Это может привести к замедлению программы, а в случае перемещения аргумента — к преждевременному освобождению ресурсов.

В зависимости от реализации стандартной библиотеки, функция 'emplace' / 'insert' перед проверкой наличия элемента с указанным ключом может создать временный объект типа 'std::pair', в который аргументы функции будут скопированы или перемещены. Начиная со стандарта С++17, для контейнеров 'std::map' и 'std::unordered_map' была добавлена функция 'try_emplace'. Она гарантирует, что если элемент с указанным ключом уже существует, то аргументы функции не будут скопированы или перемещены.

Рассмотрим пример кода:

class SomeClass
{
  std::string name, surname, descr;

public:
  // User-defined constructor
  SomeClass(std::string name, std::string surname, std::string descr);

  // ....
};

std::map<size_t, SomeClass> Cont;

bool add(size_t id,
         const std::string &name,
         const std::string &surname,
         const std::string &descr)
{
  return Cont.emplace(id, SomeClass { name, surname, descr })
             .second;
}

В примере в некоторый контейнер 'Cont' производят вставку объекта типа 'SomeClass' по ключу 'id'. Если объект по такому ключу уже ранее был добавлен, могут быть произведены следующие лишние операции:

  • 3 вызова конструктора копирования строки при конструировании временного объекта типа 'SomeClass';
  • 3 вызова конструктора перемещения строки при идеальной передаче временного объекта типа 'SomeClass' во временный объект типа 'std::pair<const size_t, SomeClass>'.

Используя функцию 'try_emplace' вместо 'emplace', можно избежать лишних операций по формированию временного объекта типа 'std::pair<const size_t, SomeClass>':

bool add(size_t id,
         const std::string &name,
         const std::string &surname,
         const std::string &descr)
{
  return Cont.try_emplace(id, SomeClass { name, surname, descr })
             .second;
}

Использование 'try_emplace' позволяет также конструировать объекты "по месту" внутри ассоциативного контейнера. В примере тип 'SomeClass' не является агрегатным и содержит определённый пользователем конструктор, поэтому можно избежать также и 3 вызовов конструкторов копирования строк:

bool add(size_t id,
         const std::string &name,
         const std::string &surname,
         const std::string &descr)
{
  return Cont.try_emplace(id, name, surname, descr)
             .second;
}

Начиная с C++20, функция 'try_emplace' работает также и с агрегатными типами:

struct SomeClass
{
  std::string name, surname, descr;
};

bool add(size_t id,
         const std::string &name,
         const std::string &surname,
         const std::string &descr)
{
  return Cont.try_emplace(id, name, surname, descr)
             .second;
}