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 благодарность за эту публикацию.