>
>
>
V823. Decreased performance. Object may…


V823. Decreased performance. Object may be created in-place in a container. Consider replacing methods: 'insert' -> 'emplace', 'push_*' -> 'emplace_*'.

Анализатор обнаружил потенциальное использование менее эффективного метода. При вставке временного объекта в контейнер при использовании методов 'insert' / 'push_*' он конструируется снаружи контейнера и затем перемещен/скопирован внутрь контейнера.

Методы 'emplace' / 'emplace_*' в свою очередь позволяют устранить один избыточный вызов конструктора перемещения / копирования и создают объект "по месту" внутри контейнера, идеально передавая параметры функции конструктору объекта.

Анализатор предлагает следующие замены:

  • insert -> emplace
  • insert_after -> emplace_after
  • push_back -> emplace_back
  • push_front -> emplace_front

Пример кода:

std::string str { "Hello, World" };
std::vector<std::string> vec;
std::forward_list<std::string> forward_list;
std::list<std::string> list;
std::map<std::string, std::string> map;

....

vec.push_back(std::string { 3, 'A' });
forward_list.push_front(std::string { str.begin(), str.begin() + 6 });
list.push_front(str.substr(7));
list.push_back(std::string { "Hello, World" });
map.insert(std::pair<std::string, std::string> { "Hello", "World" });

Оптимизированный код:

std::vector<std::string> vec;
std::forward_list<std::string> forward_list;
std::list<std::string> list;
std::map<std::string, std::string> map;

....

vector.emplace_back(3, 'A');
forward_list.emplace_front(string.begin(), string.begin() + 6);
list.emplace_front(str.begin() + 7, str.end());
list.emplace_back("Hello, World");
map.emplace("Hello", "World");

В некоторых случаях такая замена может привести к потере базовой гарантии безопасности исключений. Рассмотрим пример:

std::vector<std::unique_ptr<int>> vectUniqP;
vectUniqP.push_back(std::unique_ptr<int>(new int(0)));
auto *p = new int(1);
vectUniqP.push_back(std::unique_ptr<int>(p));

В таком случае замена 'push_back' на 'emplace_back' может привести к утечке памяти, если 'emplace_back' бросит исключение в связи с отсутствием памяти. Анализатор не выдает срабатывания на подобные случаи и не предлагает замену. Если же код был изменен на ошибочный, анализатор выдаст предупреждение V1023.

Иногда замена вызовов методов 'insert' / 'push_*' на их аналог 'emplace' / 'emplace_*' не принесет оптимизации:

std::string foo()
{
  std::string res;
  // doing some heavy stuff
  return res;
}

std::vector<std::string> vec;
....
vec.push_back(foo());

В этом примере метод 'emplace_back' будет иметь такую же эффективность, что и вставка элемента через 'push_back'. Однако, предупреждение всё равно выдаётся для единообразия. Во всех подобных ситуациях рационально сделать замену, чтобы код смотрелся единообразно и при его просмотре не приходилось каждый раз задумываться, стоит использовать 'emplace*' или нет. Если пользователь не согласен с таким подходом, он может рассматривать подобные предупреждения как ложные и подавлять их.

Примечание. К описанной здесь рекомендации стоит подходить разумно, а не формально. Например, замена

widgets.push_back(Widget(foo, bar, baz));
// на
widgets.emplace_back(Widget(foo, bar, baz));

не даёт никакого выигрыша в скорости работы программы. Более того, использование 'emplace_back' может замедлить скорость компиляции кода. Подробнее эта тема освящена в статье "Don't blindly prefer emplace_back to push_back". Наша команда выражает Arthur O'Dwyer благодарность за эту публикацию.