Kode berikut dapat membantu Anda memahami "ide gambaran besar" tentang insert()perbedaan dari emplace():
#include <iostream>
#include <unordered_map>
#include <utility>
//Foo simply outputs what constructor is called with what value.
struct Foo {
static int foo_counter; //Track how many Foo objects have been created.
int val; //This Foo object was the val-th Foo object to be created.
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;
//Print the statement to be executed and then execute it.
std::cout << "\numap.insert(std::pair<Foo, int>(foo0, d))\n";
umap.insert(std::pair<Foo, int>(foo0, d));
//Side note: 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)));
//Side note: 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();
}
Output yang saya dapatkan adalah:
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
Perhatikan itu:
Secara unordered_mapinternal selalu menyimpan Fooobjek (dan bukan, katakanlah, Foo *s) sebagai kunci, yang semuanya dihancurkan ketika unordered_mapdihancurkan. Di sini, unordered_mapkunci internal adalah foo 13, 11, 5, 10, 7, dan 9.
- Jadi secara teknis,
unordered_mapsebenarnya kita menyimpan std::pair<const Foo, int>objek, yang pada gilirannya menyimpan Fooobjek. Tetapi untuk memahami "gagasan gambaran besar" tentang emplace()perbedaannya insert()(lihat kotak yang disorot di bawah), boleh-boleh saja untuk sementara waktu membayangkan std::pairobjek ini sebagai sepenuhnya pasif. Setelah Anda memahami "gagasan gambaran besar" ini, penting untuk mencadangkan dan memahami bagaimana penggunaan std::pairobjek perantara ini dengan unordered_mapmemperkenalkan teknis yang halus namun penting.
Memasukkan masing-masing foo0, foo1dan foo2diperlukan 2 panggilan ke salah satu Foo's copy / memindahkan konstruktor dan 2 panggilan ke Foo' s destructor (seperti sekarang saya jelaskan):
Sebuah. Memasukkan masing-masing foo0dan foo1membuat objek sementara ( foo4dan foo6, masing-masing) yang destruktornya kemudian segera dipanggil setelah penyisipan selesai. Selain itu, internal Foos unordered_map (yang Foos 5 dan 7) juga memiliki destruktor mereka dipanggil ketika unordered_map dihancurkan.
b. Untuk menyisipkan foo2, kami mula-mula secara eksplisit membuat objek pasangan non-sementara (disebut pair), yang disebut Foosebagai konstruktor salin aktif foo2(dibuat foo8sebagai anggota internal pair). Kami kemudian insert()mengedit pasangan ini, yang mengakibatkan unordered_mapmemanggil konstruktor salinan lagi (aktif foo8) untuk membuat salinan internalnya sendiri ( foo9). Seperti dengan foo0 dan 1, hasil akhirnya adalah dua panggilan destruktor untuk penyisipan ini dengan satu-satunya perbedaan adalah bahwa foo8destruktor dipanggil hanya ketika kita mencapai akhir main()daripada dipanggil segera setelah insert()selesai.
Emplacing foo3menghasilkan hanya 1 salinan / memindahkan panggilan konstruktor (membuat foo10internal di unordered_map) dan hanya 1 panggilan ke Foodestruktor. (Saya akan kembali ke sini nanti).
Karena foo11, kami langsung meneruskan bilangan bulat 11 ke emplace(11, d)sehingga unordered_mapakan memanggil Foo(int)konstruktor saat eksekusi dalam emplace()metodenya. Tidak seperti pada (2) dan (3), kami bahkan tidak memerlukan objek yang sudah ada sebelumnya foountuk melakukan ini. Yang penting, perhatikan bahwa hanya 1 panggilan ke Fookonstruktor terjadi (yang dibuat foo11).
Kami kemudian langsung melewati bilangan bulat 12 ke insert({12, d}). Berbeda dengan dengan emplace(11, d)(yang mengingat hanya menghasilkan 1 panggilan ke Fookonstruktor), panggilan ini insert({12, d})menghasilkan dua panggilan ke Fookonstruktor (membuat foo12dan foo13).
Ini menunjukkan apa perbedaan "gambaran besar" utama antara insert()dan emplace():
Sedangkan menggunakan insert() hampir selalu membutuhkan konstruksi atau keberadaan beberapa Fooobjek dalam main()ruang lingkup (diikuti oleh salinan atau pindah), jika menggunakan emplace()maka setiap panggilan ke Fookonstruktor dilakukan sepenuhnya secara internal di unordered_map(yaitu di dalam ruang lingkup emplace()definisi metode). Argumen untuk kunci yang Anda lewati emplace()langsung diteruskan ke Foopanggilan konstruktor dalam unordered_map::emplace()definisi (perincian tambahan opsional: tempat objek yang baru dibangun ini segera dimasukkan ke dalam salah satu unordered_mapvariabel anggota sehingga tidak ada destruktor yang dipanggil saat daun eksekusi emplace()dan tidak ada memindahkan atau menyalin konstruktor dipanggil).
Catatan: Alasan " hampir " di " hampir selalu " di atas dijelaskan pada I) di bawah ini.
- melanjutkan: Alasan mengapa memanggil yang
umap.emplace(foo3, d)disebut Foonon-const copy constructor adalah sebagai berikut: Karena kita menggunakan emplace(), compiler tahu bahwa foo3(objek non-const Foo) dimaksudkan untuk menjadi argumen untuk beberapa Fookonstruktor. Dalam hal ini, Fookonstruktor yang paling pas adalah konstruktor salinan non-const Foo(Foo& f2). Inilah sebabnya mengapa umap.emplace(foo3, d)disebut copy constructor sementara umap.emplace(11, d)tidak.
Epilog:
I. Perhatikan bahwa satu kelebihan insert()sebenarnya setara dengan emplace() . Seperti dijelaskan dalam halaman cppreference.com ini , kelebihan template<class P> std::pair<iterator, bool> insert(P&& value)(yang kelebihan (2) dari insert()pada halaman cppreference.com ini) setara dengan emplace(std::forward<P>(value)).
II Ke mana harus pergi dari sini?
Sebuah. Bermain-main dengan kode sumber di atas dan belajar dokumentasi untuk insert()(misalnya di sini ) dan emplace()(misalnya di sini ) yang ditemukan online. Jika Anda menggunakan IDE seperti gerhana atau NetBeans maka Anda dapat dengan mudah mendapatkan IDE untuk memberi tahu Anda yang kelebihan beban insert()atau emplace()sedang dipanggil (dalam gerhana, pertahankan kursor mouse Anda stabil selama panggilan fungsi sebentar). Berikut beberapa kode untuk dicoba:
std::cout << "\numap.insert({{" << Foo::foo_counter << ", d}})\n";
umap.insert({{Foo::foo_counter, d}});
//but umap.emplace({{Foo::foo_counter, d}}); results in a compile error!
std::cout << "\numap.insert(std::pair<const Foo, int>({" << Foo::foo_counter << ", d}))\n";
umap.insert(std::pair<const Foo, int>({Foo::foo_counter, d}));
//The above uses Foo(int) and then Foo(const Foo &), as expected. but the
// below call uses Foo(int) and the move constructor Foo(Foo&&).
//Do you see why?
std::cout << "\numap.insert(std::pair<Foo, int>({" << Foo::foo_counter << ", d}))\n";
umap.insert(std::pair<Foo, int>({Foo::foo_counter, d}));
//Not only that, but even more interesting is how the call below uses all
// three of Foo(int) and the Foo(Foo&&) move and Foo(const Foo &) copy
// constructors, despite the below call's only difference from the call above
// being the additional { }.
std::cout << "\numap.insert({std::pair<Foo, int>({" << Foo::foo_counter << ", d})})\n";
umap.insert({std::pair<Foo, int>({Foo::foo_counter, d})});
//Pay close attention to the subtle difference in the effects of the next
// two calls.
int cur_foo_counter = Foo::foo_counter;
std::cout << "\numap.insert({{cur_foo_counter, d}, {cur_foo_counter+1, d}}) where "
<< "cur_foo_counter = " << cur_foo_counter << "\n";
umap.insert({{cur_foo_counter, d}, {cur_foo_counter+1, d}});
std::cout << "\numap.insert({{Foo::foo_counter, d}, {Foo::foo_counter+1, d}}) where "
<< "Foo::foo_counter = " << Foo::foo_counter << "\n";
umap.insert({{Foo::foo_counter, d}, {Foo::foo_counter+1, d}});
//umap.insert(std::initializer_list<std::pair<Foo, int>>({{Foo::foo_counter, d}}));
//The call below works fine, but the commented out line above gives a
// compiler error. It's instructive to find out why. The two calls
// differ by a "const".
std::cout << "\numap.insert(std::initializer_list<std::pair<const Foo, int>>({{" << Foo::foo_counter << ", d}}))\n";
umap.insert(std::initializer_list<std::pair<const Foo, int>>({{Foo::foo_counter, d}}));
Anda akan segera melihat bahwa kelebihan std::pairkonstruktor (lihat referensi ) yang akhirnya digunakan oleh unordered_mapdapat memiliki efek penting pada berapa banyak objek yang disalin, dipindahkan, dibuat, dan / atau dihancurkan serta ketika semua ini terjadi.
b. Lihat apa yang terjadi ketika Anda menggunakan beberapa kelas kontainer lain (misalnya std::setatau std::unordered_multiset) sebagai gantinya std::unordered_map.
c. Sekarang gunakan Gooobjek (hanya salinan berganti nama dari Foo) alih-alih intsebagai jenis rentang dalam unordered_map(yaitu menggunakan unordered_map<Foo, Goo>alih-alih unordered_map<Foo, int>) dan melihat berapa banyak dan yang Goodisebut konstruktor. (Spoiler: ada efek tetapi tidak terlalu dramatis.)