masukkan vs emplace vs operator [] di c ++ map

192

Saya menggunakan peta untuk pertama kalinya dan saya menyadari bahwa ada banyak cara untuk memasukkan elemen. Anda dapat menggunakan emplace(), operator[]atau insert(), plus varian seperti menggunakan value_typeatau make_pair. Meskipun ada banyak informasi tentang semuanya dan pertanyaan tentang kasus-kasus tertentu, saya masih tidak dapat memahami gambaran besarnya. Jadi, dua pertanyaan saya adalah:

  1. Apa keunggulan masing-masing dari mereka dibanding yang lain?

  2. Apakah ada kebutuhan untuk menambahkan emplace ke standar? Adakah sesuatu yang sebelumnya tidak mungkin tanpa itu?

Capuano Jerman
sumber
1
Semantik penempatan memungkinkan konversi eksplisit dan inisialisasi langsung.
Kerrek SB
3
Sekarang operator[]didasarkan pada try_emplace. Mungkin perlu disebutkan insert_or_assignjuga.
FrankHB
@ FrankHB jika Anda (atau orang lain) menambahkan jawaban yang terbaru, saya bisa mengubah yang diterima.
Capuano Jerman

Jawaban:

229

Khusus untuk peta, opsi lama hanya dua: operator[]dan insert(rasa berbeda insert). Jadi saya akan mulai menjelaskannya.

The operator[]adalah menemukan-atau-add operator. Ini akan mencoba menemukan elemen dengan kunci yang diberikan di dalam peta, dan jika ada, itu akan mengembalikan referensi ke nilai yang disimpan. Jika tidak, itu akan membuat elemen baru dimasukkan di tempatnya dengan inisialisasi default dan mengembalikan referensi untuk itu.

The insertfunction (dalam rasa elemen tunggal) mengambil value_type( std::pair<const Key,Value>), menggunakan kunci ( firstanggota) dan mencoba untuk memasukkannya. Karena std::maptidak memungkinkan untuk duplikat jika ada elemen yang ada tidak akan memasukkan apa pun.

Perbedaan pertama antara keduanya adalah yang operator[]harus mampu membangun nilai inisialisasi default , dan karenanya tidak dapat digunakan untuk tipe nilai yang tidak dapat diinisialisasi default. Perbedaan kedua antara keduanya adalah apa yang terjadi ketika sudah ada elemen dengan kunci yang diberikan. The insertfungsi tidak akan mengubah keadaan peta, melainkan mengembalikan iterator ke elemen (dan falsemenunjukkan bahwa itu tidak dimasukkan).

// 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

Dalam kasus insertargumen adalah objek value_type, yang dapat dibuat dengan berbagai cara. Anda dapat langsung membangunnya dengan jenis yang sesuai atau melewatkan objek apa pun dari mana value_typedapat dibangun, yang merupakan tempat std::make_pairbermain, karena memungkinkan untuk penciptaan std::pairobjek sederhana , meskipun mungkin bukan yang Anda inginkan ...

Efek bersih dari panggilan berikut serupa :

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

Tapi sebenarnya tidak sama ... [1] dan [2] sebenarnya setara. Dalam kedua kasus, kode membuat objek sementara dengan tipe yang sama ( std::pair<const K,V>) dan meneruskannya ke insertfungsi. The insertfungsi akan membuat simpul yang tepat dalam pohon pencarian biner dan kemudian salin value_typebagian dari argumen ke node. Keuntungan menggunakan value_typeadalah, yah, value_typeselalu cocok value_type , Anda tidak bisa salah ketik jenis std::pairargumen!

Perbedaannya ada pada [3]. Fungsi std::make_pairadalah fungsi templat yang akan membuat a std::pair. Tanda tangan adalah:

template <typename T, typename U>
std::pair<T,U> make_pair(T const & t, U const & u );

Saya sengaja tidak memberikan argumen templat kepada std::make_pair, karena itu adalah penggunaan umum. Dan implikasinya adalah bahwa argumen templat disimpulkan dari panggilan, dalam hal ini menjadi T==K,U==V, sehingga panggilan untuk std::make_pairakan mengembalikan std::pair<K,V>(perhatikan yang hilang const). Tanda tangan mengharuskan value_typeyang dekat tetapi tidak sama dengan nilai yang dikembalikan dari panggilan ke std::make_pair. Karena cukup dekat itu akan membuat sementara dari jenis yang benar dan salin menginisialisasi itu. Itu pada gilirannya akan disalin ke node, membuat total dua salinan.

Ini dapat diperbaiki dengan memberikan argumen templat:

m.insert( std::make_pair<const K,V>(t,u) );  // 4

Tapi itu masih rentan kesalahan dengan cara yang sama yang secara eksplisit mengetikkan tipe dalam kasus [1].

Hingga saat ini, kami memiliki berbagai cara panggilan insertyang membutuhkan penciptaan secara value_typeeksternal dan salinan objek itu ke dalam wadah. Sebagai alternatif, Anda dapat menggunakan operator[]jika jenisnya dapat dibangun dan ditetapkan secara default (dengan sengaja hanya berfokus pada m[k]=v), dan itu memerlukan inisialisasi default dari satu objek dan salinan nilai ke objek tersebut.

Dalam C ++ 11, dengan templat variadic dan penerusan yang sempurna ada cara baru untuk menambahkan elemen ke dalam wadah dengan cara emplacing (membuat di tempat). emplaceFungsi - fungsi dalam wadah yang berbeda pada dasarnya melakukan hal yang sama: alih-alih mendapatkan sumber untuk menyalin ke wadah, fungsi tersebut mengambil parameter yang akan diteruskan ke konstruktor objek yang disimpan dalam wadah.

m.emplace(t,u);               // 5

Dalam [5], std::pair<const K, V>itu tidak dibuat dan diteruskan ke emplace, melainkan referensi ke tdan uobjek diteruskan ke emplaceyang meneruskannya ke konstruktor dari value_typesub-objek di dalam struktur data. Dalam hal ini tidak ada salinan std::pair<const K,V>yang dilakukan sama sekali, yang merupakan keuntungan dari emplacelebih dari alternatif C ++ 03. Seperti dalam kasus insertitu tidak akan menimpa nilai di peta.


Pertanyaan menarik yang belum saya pikirkan adalah bagaimana emplacesebenarnya bisa diterapkan untuk peta, dan itu bukan masalah sederhana dalam kasus umum.

David Rodríguez - dribeas
sumber
5
Ini ditunjukkan dalam jawaban, tetapi peta [] = val akan menimpa nilai sebelumnya jika ada.
dk123
pertanyaan yang lebih menarik dalam pengertian saya, adalah bahwa itu melayani tujuan kecil. Karena Anda menyimpan salinan pasangan, yang baik karena tidak ada salinan pasangan berarti tidak ada mapped_typesalinan isntance. Apa yang kita inginkan, adalah menempatkan konstruksi mapped_typepasangan, dan menempatkan konstruksi pasangan di peta. Oleh karena itu, std::pair::emplacefungsi, dan dukungan penerusannya map::emplacehilang. Dalam bentuk saat ini, Anda masih harus memberikan mapped_type yang dikonstruksi ke konstruktor pasangan yang akan menyalinnya, sekali. lebih baik dari dua kali, tetapi masih tidak bagus.
v.oddou
sebenarnya saya mengubah komentar itu, di C ++ 11 ada pasangan template yang melayani tujuan yang sama persis daripada di kasus konstruksi argumen 1. dan beberapa konstruksi yang aneh, seperti yang mereka sebut, menggunakan tuple untuk meneruskan argumen, sehingga kita masih bisa memiliki penerusan yang sempurna tampaknya.
v.oddou
Sepertinya ada bug kinerja yang disisipkan di unordered_map dan peta: tautan
Deqing
1
Mungkin menyenangkan untuk memperbarui ini dengan info insert_or_assigndan try_emplace(keduanya dari C ++ 17), yang membantu mengisi beberapa kesenjangan dalam fungsionalitas dari metode yang ada.
ShadowRanger
15

Emplace: Mengambil keuntungan dari referensi nilai untuk menggunakan objek aktual yang telah Anda buat. Ini berarti bahwa tidak ada copy atau move constructor yang dipanggil, bagus untuk objek BESAR! O (log (N)) waktu.

Sisipkan: Memiliki kelebihan untuk referensi nilai standar dan referensi nilai, serta iterator ke daftar elemen yang akan disisipkan, dan "memberi petunjuk" tentang posisi yang dimiliki elemen. Penggunaan iterator "hint" dapat membawa waktu penyisipan turun ke waktu yang ditentukan, jika tidak maka itu adalah waktu O (log (N)).

Operator []: Memeriksa untuk melihat apakah objek itu ada, dan jika ada, memodifikasi referensi ke objek ini, jika tidak menggunakan kunci dan nilai yang diberikan untuk memanggil make_pair pada dua objek, dan kemudian melakukan pekerjaan yang sama dengan fungsi menyisipkan. Ini saatnya O (log (N)).

make_pair: Tidak lebih dari membuat pasangan.

Tidak ada "kebutuhan" untuk menambahkan emplace ke standar. Di c ++ 11 saya percaya && jenis referensi ditambahkan. Ini menghapus keharusan untuk memindahkan semantik, dan memungkinkan optimalisasi beberapa jenis manajemen memori tertentu. Secara khusus, referensi nilai. Operator insert (value_type &&) yang kelebihan beban tidak memanfaatkan semantik in_place, dan karenanya jauh lebih efisien. Sementara itu menyediakan kemampuan berurusan dengan referensi nilai, itu mengabaikan tujuan utama mereka, yang ada di tempat konstruksi objek.

ChrisCM
sumber
4
" Tidak ada" kebutuhan "untuk menambahkan emplace ke standar." Ini benar-benar salah. emplace()hanyalah satu-satunya cara untuk memasukkan elemen yang tidak dapat disalin atau dipindahkan. (& ya, mungkin, untuk paling efisien menyisipkan yang menyalin dan memindahkan konstruktor lebih mahal daripada konstruksi, jika hal seperti itu ada) Sepertinya Anda salah paham: ini bukan tentang " [mengambil] keuntungan dari referensi nilai untuk menggunakan objek aktual yang telah Anda buat "; belum ada objek yang dibuat, & Anda meneruskan mapargumen yang diperlukan untuk membuatnya di dalam dirinya sendiri. Anda tidak membuat objek.
underscore_d
10

Terlepas dari peluang pengoptimalan dan sintaksis yang lebih sederhana, perbedaan penting antara penyisipan dan penempatan adalah bahwa yang terakhir memungkinkan konversi eksplisit . (Ini melintasi seluruh perpustakaan standar, bukan hanya untuk peta.)

Berikut ini contoh untuk ditunjukkan:

#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
}

Ini memang detail yang sangat spesifik, tetapi ketika Anda berhadapan dengan rantai konversi yang ditentukan pengguna, ada baiknya mengingat hal ini.

Kerrek SB
sumber
Bayangkan bahwa foo membutuhkan dua int di ctor daripada satu. Apakah Anda dapat menggunakan panggilan ini? v.emplace(v.end(), 10, 10); ... atau sekarang Anda perlu menggunakan v.emplace(v.end(), foo(10, 10) ); :?
Kaitain
Saya tidak memiliki akses ke kompiler sekarang, tetapi saya akan berasumsi bahwa ini berarti bahwa kedua versi akan berfungsi. Hampir semua contoh yang Anda lihat emplacemenggunakan kelas yang mengambil parameter tunggal. IMO itu sebenarnya akan membuat sifat sintaks variadic emplace jauh lebih jelas jika beberapa parameter digunakan dalam contoh.
Kaitain
9

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:

  1. 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.
  2. 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.

  3. 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).

  4. 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).

  5. 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.

  1. 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.)

Matthew K.
sumber