В чем разница между unordered_map:: emplace и unordered_map:: insert в С++?

В чем разница между std::unordered_map::emplace и std::unordered_map::insert в C++?

Ответ 1

unordered_map::insert копирует или перемещает пару ключ-значение в контейнер. Он перегружен, чтобы принимать ссылку-на-const или ссылку rvalue:

std::pair<iterator,bool> insert(const std::pair<const Key, T>& value);

template<class P>
std::pair<iterator,bool> insert(P&& value);

unordered_map::emplace позволяет избежать ненужных копий или движений, создавая элемент на месте. Он использует совершенную переадресацию и вариационный шаблон для форвардных аргументов конструктору пары ключ-значение:

template<class... Args>
std::pair<iterator,bool> emplace(Args&&... args);

Но между этими двумя функциями существует много перекрытий. emplace можно использовать для пересылки конструктору copy/move пары ключ-значение, которая позволяет использовать его так же, как insert. Это означает, что использование emplace не гарантирует, что вы избежите копирования или перемещения. Кроме того, версия insert, которая принимает rvalue-reference, фактически шаблонизирована и принимает любой тип P, так что пара ключ-значение конструктивна из P.

Скотт Мейерс говорит:

В принципе, функции размещения иногда должны быть более эффективными чем их вставные аналоги, и они никогда не должны быть меньше эффективным.

( Изменить: Говард Хиннант провел несколько экспериментов, которые иногда показывали insert быстрее, чем emplace).

Если вы действительно хотите скопировать/переместить в контейнер, может быть разумно использовать insert, потому что вы, скорее всего, получите ошибку компиляции, если вы передадите неправильные аргументы. Вы должны быть более осторожны, вы передаете правильные аргументы функциям размещения.

Большинство реализаций unordered_map::emplace приведет к динамическому распределению памяти для новой пары, даже если карта уже содержит элемент с этим ключом, а emplace завершится с ошибкой. Это означает, что если есть вероятность, что сбой emplace, вы можете получить лучшую производительность с помощью вставки, чтобы избежать ненужных распределений динамической памяти.

Небольшой пример:

#include <unordered_map>
#include <iostream>

int main() {
  auto employee1 = std::pair<int, std::string>{1, "John Smith"};

  auto employees = std::unordered_map<int, std::string>{};

  employees.insert(employee1);  // copy insertion
  employees.insert(std::make_pair(2, "Mary Jones"));  // move insertion 
  employees.emplace(3, "James Brown");  // construct in-place

  for (const auto& employee : employees)
    std::cout << employee.first << ": " << employee.second << "\n";
}

Edit2: По запросу. Также возможно использовать unordered_map::emplace с ключом или значением, которое принимает более одного параметра конструктора. Используя std::pair кусочный конструктор, вы все равно можете избежать ненужных копий или перемещений.

#include <unordered_map>
#include <iostream>

struct Employee {
  std::string firstname;
  std::string lastname;
  Employee(const std::string& firstname, const std::string& lastname) 
  : firstname(firstname), lastname(lastname){}    
};

int main() {
  auto employees = std::unordered_map<int, Employee>{};
  auto employee1 = std::pair<int, Employee>{1, Employee{"John", "Smith"}};

  employees.insert(employee1);  // copy insertion
  employees.insert(std::make_pair(2, Employee{"Mary", "Jones"}));  // move insertion
  employees.emplace(3, Employee("Sam", "Thomas")); // emplace with pre-constructed Employee
  employees.emplace(std::piecewise_construct,
                    std::forward_as_tuple(4),
                    std::forward_as_tuple("James", "Brown"));  // construct in-place
}