вставки против установки против оператора[] в C++ карте
я использую карты в первый раз, и я понял, что есть много способов, чтобы вставить элемент. Вы можете использовать emplace()
,operator[]
или insert()
, плюс варианты, такие как использование value_type
или make_pair
. Хотя есть много информации обо всех из них и вопросы о конкретных случаях, я все еще не могу понять общую картину.
Итак, мои два вопроса:
в чем преимущество каждого из них перед другими?
есть ли необходимость добавления установки на стандартный? Есть ли что-то, что было невозможно раньше без него?
4 ответа:
в частном случае карты старых вариантов было всего два:
operator[]
иinsert
(различных вкусовinsert
). Поэтому я начну объяснять это.The
operator[]
это найти или добавить оператора. Он попытается найти элемент с заданным ключом внутри карты, и если он существует, он вернет ссылку на сохраненное значение. Если это не так, он создаст новый элемент, вставленный на место с инициализацией по умолчанию и вернет ссылку на оно.The
insert
функция (в одном элементе аромата) принимаетvalue_type
(std::pair<const Key,Value>
), он использует ключ (first
member) и пытается вставить его. Потому чтоstd::map
не допускает дубликатов, если есть существующий элемент, он ничего не вставит.первое различие между ними заключается в том, что
operator[]
должен быть в состоянии построить инициализированный по умолчанию стоимостью, и поэтому он непригоден для типов значений, которые не могут быть по умолчанию инициализированный. Второе различие между ними заключается в том, что происходит, когда уже есть элемент с данным ключом. Элементinsert
функция не будет изменять состояние карты, но вместо этого возвращает итератор к элементу (иfalse
указывает, что он не был вставлен).// assume m is std::map<int,int> already has an element with key 5 and value 0 m[5] = 10; // postcondition: m[5] == 10 m.insert(std::make_pair(5,15)); // m[5] is still 10
в случае
insert
аргумент является объектомvalue_type
, которые могут быть созданы по-разному. Вы можете непосредственно построить его с соответствующим типом или передать любой объект из что заvalue_type
может быть построен, гдеstd::make_pair
вступает в игру, как это позволяет для простого созданияstd::pair
объекты, хотя это наверное не то, что вы хотите...чистый эффект следующих вызовов как:
K t; V u; std::map<K,V> m; // std::map<K,V>::value_type is std::pair<const K,V> m.insert( std::pair<const K,V>(t,u) ); // 1 m.insert( std::map<K,V>::value_type(t,u) ); // 2 m.insert( std::make_pair(t,u) ); // 3
но на самом деле это не одно и то же... [1] и [2] фактически эквивалентны. В обоих случаях код создает временный объект одного типа (
std::pair<const K,V>
) и передает его в
Emplace: использует ссылку rvalue для использования фактических объектов, которые вы уже создали. Это означает, что конструктор копирования или перемещения не вызывается, хорошо для больших объектов! O(log (N)) время.
Insert: имеет перегрузки для стандартной ссылки lvalue и ссылки rvalue, а также итераторы для списков элементов для вставки и "подсказки" относительно позиции, к которой принадлежит элемент. Использование итератора "подсказка" может привести время вставки занимает до contant time, в противном случае это время O(log(N)).
Operator []: проверяет, существует ли объект, и если да, изменяет ссылку на этот объект, в противном случае использует предоставленный ключ и значение для вызова make_pair на двух объектах, а затем выполняет ту же работу, что и функция insert. Это время O (log (N)).
make_pair: делает немного больше, чем сделать пару.
не было никакой "необходимости" для добавления emplace к стандарту. В c++11 я считаю, что & & тип ссылки был добавлен. Это устранило необходимость в семантике перемещения и позволило оптимизировать определенный тип управления памятью. В частности, ссылка rvalue. Перегруженный оператор insert(value_type &&) не использует преимущества семантики in_place и поэтому является гораздо менее эффективным. Хотя он обеспечивает возможность работы с ссылками rvalue, он игнорирует их ключевое назначение, которое заключается в построении объектов.
помимо возможностей оптимизации и более простого синтаксиса, важное различие между вставкой и установкой заключается в том, что последнее позволяет явно преобразования. (Это касается всей стандартной библиотеки, а не только карт.)
вот пример для демонстрации:
#include <vector> struct foo { explicit foo(int); }; int main() { std::vector<foo> v; v.emplace(v.end(), 10); // Works //v.insert(v.end(), 10); // Error, not explicit v.insert(v.end(), foo(10)); // Also works }
это, по общему признанию, очень конкретная деталь, но когда вы имеете дело с цепочками пользовательских преобразований, стоит иметь это в виду.
следующий код может помочь вам понять "общую идею картины" как
insert()
отличается отemplace()
:#include <iostream> #include <unordered_map> #include <utility> struct Foo { static int foo_counter; int val; Foo() { val = foo_counter++; std::cout << "Foo() with val: " << val << '\n'; } Foo(int value) : val(value) { foo_counter++; std::cout << "Foo(int) with val: " << val << '\n'; } Foo(Foo& f2) { val = foo_counter++; std::cout << "Foo(Foo &) with val: " << val << " \tcreated from: \t" << f2.val << '\n'; } Foo(const Foo& f2) { val = foo_counter++; std::cout << "Foo(const Foo &) with val: " << val << " \tcreated from: \t" << f2.val << '\n'; } Foo(Foo&& f2) { val = foo_counter++; std::cout << "Foo(Foo&&) moving: " << f2.val << " \tand changing it to:\t" << val << '\n'; } ~Foo() { std::cout << "~Foo() destroying: " << val << '\n'; } Foo& operator=(const Foo& rhs) { std::cout << "Foo& operator=(const Foo& rhs) with rhs.val: " << rhs.val << " \tcalled with lhs.val = \t" << val << " \tChanging lhs.val to: \t" << rhs.val << '\n'; val = rhs.val; return *this; } bool operator==(const Foo &rhs) const { return val == rhs.val; } bool operator<(const Foo &rhs) const { return val < rhs.val; } }; int Foo::foo_counter = 0; //Create a hash function for Foo in order to use Foo with unordered_map namespace std { template<> struct hash<Foo> { std::size_t operator()(const Foo &f) const { return std::hash<int>{}(f.val); } }; } int main() { std::unordered_map<Foo, int> umap; Foo foo0, foo1, foo2, foo3; int d; std::cout << "\numap.insert(std::pair<Foo, int>(foo0, d))\n"; umap.insert(std::pair<Foo, int>(foo0, d)); //equiv. to: umap.insert(std::make_pair(foo0, d)); std::cout << "\numap.insert(std::move(std::pair<Foo, int>(foo1, d)))\n"; umap.insert(std::move(std::pair<Foo, int>(foo1, d))); //equiv. to: umap.insert(std::make_pair(foo1, d)); std::cout << "\nstd::pair<Foo, int> pair(foo2, d)\n"; std::pair<Foo, int> pair(foo2, d); std::cout << "\numap.insert(pair)\n"; umap.insert(pair); std::cout << "\numap.emplace(foo3, d)\n"; umap.emplace(foo3, d); std::cout << "\numap.emplace(11, d)\n"; umap.emplace(11, d); std::cout << "\numap.insert({12, d})\n"; umap.insert({12, d}); std::cout.flush(); }
вывод, который я получил, был:
Foo() with val: 0 Foo() with val: 1 Foo() with val: 2 Foo() with val: 3 umap.insert(std::pair<Foo, int>(foo0, d)) Foo(Foo &) with val: 4 created from: 0 Foo(Foo&&) moving: 4 and changing it to: 5 ~Foo() destroying: 4 umap.insert(std::move(std::pair<Foo, int>(foo1, d))) Foo(Foo &) with val: 6 created from: 1 Foo(Foo&&) moving: 6 and changing it to: 7 ~Foo() destroying: 6 std::pair<Foo, int> pair(foo2, d) Foo(Foo &) with val: 8 created from: 2 umap.insert(pair) Foo(const Foo &) with val: 9 created from: 8 umap.emplace(foo3, d) Foo(Foo &) with val: 10 created from: 3 umap.emplace(11, d) Foo(int) with val: 11 umap.insert({12, d}) Foo(int) with val: 12 Foo(const Foo &) with val: 13 created from: 12 ~Foo() destroying: 12 ~Foo() destroying: 8 ~Foo() destroying: 3 ~Foo() destroying: 2 ~Foo() destroying: 1 ~Foo() destroying: 0 ~Foo() destroying: 13 ~Foo() destroying: 11 ~Foo() destroying: 5 ~Foo() destroying: 10 ~Foo() destroying: 7 ~Foo() destroying: 9
обратите внимание, что:
An
unordered_map
всегда внутренне магазинахFoo
объектами (а не, скажем,Foo *
S), как ключи, которые все разрушили, когдаunordered_map
разрушается. Здесьunordered_map
внутренние ключи были foos 13, 11, 5, 10, 7, и 9.
- так что технически, наш
unordered_map
на самом деле магазиныstd::pair<const Foo, int>
объекты, которые, в свою очередь, хранитьFoo
объекты. Но чтобы понять "общую идею картины" какemplace()
отличается отinsert()
(см. выделенное поле ниже), это нормально временно представьте себе, этоstd::pair
объект как полностью пассивный. Как только вы поймете эту "большую идею картины", важно затем создать резервную копию и понять, как использовать этого посредника