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

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


person Harsh M. Shah    schedule 19.10.2014    source источник
comment
Взято из справки: Осторожное использование emplace позволяет новый элемент, который нужно создать, избегая ненужных операций копирования или перемещения. Конструктор нового элемента (т. Е. std::pair<const Key, T>) вызывается с точно такими же аргументами, которые были переданы в emplace, пересылаемые через std::forward<Args>(args)....   -  person chris    schedule 19.10.2014
comment
emplace создает новый объект, а insert принимает существующий объект. Параметры отличаются, поскольку emplace принимает аргументы конструктора. Если у вас еще нет экземпляра для вставки, используйте emplace.   -  person Neil Kirk    schedule 19.10.2014


Ответы (2)


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 можно использовать для перенаправления в конструктор копирования / перемещения пары "ключ-значение", что позволяет использовать его так же, как insert. Это означает, что использование emplace не гарантирует, что вы избежите копий или перемещений. Кроме того, версия insert, которая принимает rvalue-ссылку, фактически является шаблоном и принимает любой тип 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
}
person Chris Drew    schedule 19.10.2014
comment
Функции размещения часто более эффективны, чем их аналоги для вставки, и они никогда не менее эффективны. Ховард Хиннант измерил нечто иное: http://htmlpreview.github.io/?https://github.com/HowardHinnant/papers/blob/master/insert_vs_emplace.html См. также : groups.google.com/a/isocpp.org/forum/?fromgroups#!searchin/ - person dyp; 19.10.2014
comment
@Yakk А, прости. Надеюсь, это сработает: groups.google.com/ a / isocpp.org / d / topic / std-обсуждение / emplace для ассоциативных контейнеров обычно создает элемент (а не только ключ) из аргументов, даже если он не вставлен. Поэтому, если у вас уже есть элемент (вне ассоциативного контейнера) и вы хотите его скопировать, insert может быть более эффективным. - person dyp; 19.10.2014
comment
@dyp аккуратно. Поэтому для эффективности нам нужна частичная конструкция и тип pair, которую мы можем завершить за второй проход. И то же самое, наверное, для tuples. - person Yakk - Adam Nevraumont; 19.10.2014
comment
Увы, никто никогда не показывает пример карты ‹T1, T2›, где конструкторы для T1 и T2 принимают более одного аргумента. Я часто использую такие вещи, как map ‹string, classWithTwoParamConstructor›. - person jzions; 01.05.2015
comment
Функции размещения часто более эффективны, чем их аналоги для вставки, и они никогда не менее эффективны. Есть ли у вас какие-нибудь цитаты по этому поводу (которые не устарели)? Фактически, на встрече C ++ 2014 Скотт Мейерс объяснил, что это удивительное поведение является основной причиной того, что глава книги названа «Рассматривать размещение вместо вставки вместо предпочтения размещения…». - person Arne Vogel; 18.05.2015
comment
@ArneVogel Я обновил цитату, чтобы она соответствовала печатной версии Effective Modern C ++ (элемент 42, стр. 301). Я думаю, что другая причина, по которой это следует учитывать, а не предпочитать, заключается в том, что когда вы используете функцию размещения, вы должны быть осторожны, чтобы убедиться, что вы передаете правильные аргументы, потому что функции размещения могут выполнять преобразования, которые будут отклонены функциями вставки. - person Chris Drew; 18.05.2015
comment
@jzions Я добавил пример с T2, который построен с более чем одним аргументом. - person Chris Drew; 31.05.2016

Разница между emplace() и _ 2_ уже хорошо объяснен в Ответ Криса Дрю. Однако для полноты картины я хотел бы добавить, что, поскольку C ++ 17 std::unordered_map предоставляет два новых метода вставки: _ 4_ и _ 5_. Позвольте мне кратко резюмировать эти методы:

  • try_emplace() - это улучшенная версия emplace(). В отличие от emplace(), try_emplace() не изменяет свои аргументы (из-за операций перемещения), если вставка не удалась из-за ключа, уже существующего в unordered_map.
  • insert_or_assign() - это улучшенная версия operator[]. В отличие от operator[], insert_or_assign() не требует, чтобы тип значения unordered_map был конструктивным по умолчанию.

Я написал более подробный ответ на упомянутые выше новые методы вставки std::map здесь. Этот ответ также относится к std::unordered_map.

Простой пример кода на Coliru

person honk    schedule 11.08.2020