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_type
atau 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:
Apa keunggulan masing-masing dari mereka dibanding yang lain?
Apakah ada kebutuhan untuk menambahkan emplace ke standar? Adakah sesuatu yang sebelumnya tidak mungkin tanpa itu?
operator[]
didasarkan padatry_emplace
. Mungkin perlu disebutkaninsert_or_assign
juga.Jawaban:
Khusus untuk peta, opsi lama hanya dua:
operator[]
daninsert
(rasa berbedainsert
). 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
insert
function (dalam rasa elemen tunggal) mengambilvalue_type
(std::pair<const Key,Value>
), menggunakan kunci (first
anggota) dan mencoba untuk memasukkannya. Karenastd::map
tidak 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. Theinsert
fungsi tidak akan mengubah keadaan peta, melainkan mengembalikan iterator ke elemen (danfalse
menunjukkan bahwa itu tidak dimasukkan).Dalam kasus
insert
argumen adalah objekvalue_type
, yang dapat dibuat dengan berbagai cara. Anda dapat langsung membangunnya dengan jenis yang sesuai atau melewatkan objek apa pun dari manavalue_type
dapat dibangun, yang merupakan tempatstd::make_pair
bermain, karena memungkinkan untuk penciptaanstd::pair
objek sederhana , meskipun mungkin bukan yang Anda inginkan ...Efek bersih dari panggilan berikut serupa :
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 keinsert
fungsi. Theinsert
fungsi akan membuat simpul yang tepat dalam pohon pencarian biner dan kemudian salinvalue_type
bagian dari argumen ke node. Keuntungan menggunakanvalue_type
adalah, yah,value_type
selalu cocokvalue_type
, Anda tidak bisa salah ketik jenisstd::pair
argumen!Perbedaannya ada pada [3]. Fungsi
std::make_pair
adalah fungsi templat yang akan membuat astd::pair
. Tanda tangan adalah: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 menjadiT==K,U==V
, sehingga panggilan untukstd::make_pair
akan mengembalikanstd::pair<K,V>
(perhatikan yang hilangconst
). Tanda tangan mengharuskanvalue_type
yang dekat tetapi tidak sama dengan nilai yang dikembalikan dari panggilan kestd::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:
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
insert
yang membutuhkan penciptaan secaravalue_type
eksternal dan salinan objek itu ke dalam wadah. Sebagai alternatif, Anda dapat menggunakanoperator[]
jika jenisnya dapat dibangun dan ditetapkan secara default (dengan sengaja hanya berfokus padam[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).
emplace
Fungsi - 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.Dalam [5],
std::pair<const K, V>
itu tidak dibuat dan diteruskan keemplace
, melainkan referensi ket
danu
objek diteruskan keemplace
yang meneruskannya ke konstruktor darivalue_type
sub-objek di dalam struktur data. Dalam hal ini tidak ada salinanstd::pair<const K,V>
yang dilakukan sama sekali, yang merupakan keuntungan dariemplace
lebih dari alternatif C ++ 03. Seperti dalam kasusinsert
itu tidak akan menimpa nilai di peta.Pertanyaan menarik yang belum saya pikirkan adalah bagaimana
emplace
sebenarnya bisa diterapkan untuk peta, dan itu bukan masalah sederhana dalam kasus umum.sumber
mapped_type
salinan isntance. Apa yang kita inginkan, adalah menempatkan konstruksimapped_type
pasangan, dan menempatkan konstruksi pasangan di peta. Oleh karena itu,std::pair::emplace
fungsi, dan dukungan penerusannyamap::emplace
hilang. 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.insert_or_assign
dantry_emplace
(keduanya dari C ++ 17), yang membantu mengisi beberapa kesenjangan dalam fungsionalitas dari metode yang ada.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.
sumber
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 meneruskanmap
argumen yang diperlukan untuk membuatnya di dalam dirinya sendiri. Anda tidak membuat objek.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:
Ini memang detail yang sangat spesifik, tetapi ketika Anda berhadapan dengan rantai konversi yang ditentukan pengguna, ada baiknya mengingat hal ini.
sumber
v.emplace(v.end(), 10, 10);
... atau sekarang Anda perlu menggunakanv.emplace(v.end(), foo(10, 10) );
:?emplace
menggunakan kelas yang mengambil parameter tunggal. IMO itu sebenarnya akan membuat sifat sintaks variadic emplace jauh lebih jelas jika beberapa parameter digunakan dalam contoh.Kode berikut dapat membantu Anda memahami "ide gambaran besar" tentang
insert()
perbedaan dariemplace()
:Output yang saya dapatkan adalah:
Perhatikan itu:
Secara
unordered_map
internal selalu menyimpanFoo
objek (dan bukan, katakanlah,Foo *
s) sebagai kunci, yang semuanya dihancurkan ketikaunordered_map
dihancurkan. Di sini,unordered_map
kunci internal adalah foo 13, 11, 5, 10, 7, dan 9.unordered_map
sebenarnya kita menyimpanstd::pair<const Foo, int>
objek, yang pada gilirannya menyimpanFoo
objek. Tetapi untuk memahami "gagasan gambaran besar" tentangemplace()
perbedaannyainsert()
(lihat kotak yang disorot di bawah), boleh-boleh saja untuk sementara waktu membayangkanstd::pair
objek ini sebagai sepenuhnya pasif. Setelah Anda memahami "gagasan gambaran besar" ini, penting untuk mencadangkan dan memahami bagaimana penggunaanstd::pair
objek perantara ini denganunordered_map
memperkenalkan teknis yang halus namun penting.Memasukkan masing-masing
foo0
,foo1
danfoo2
diperlukan 2 panggilan ke salah satuFoo
's copy / memindahkan konstruktor dan 2 panggilan keFoo
' s destructor (seperti sekarang saya jelaskan):Sebuah. Memasukkan masing-masing
foo0
danfoo1
membuat objek sementara (foo4
danfoo6
, masing-masing) yang destruktornya kemudian segera dipanggil setelah penyisipan selesai. Selain itu, internalFoo
s unordered_map (yangFoo
s 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 (disebutpair
), yang disebutFoo
sebagai konstruktor salin aktiffoo2
(dibuatfoo8
sebagai anggota internalpair
). Kami kemudianinsert()
mengedit pasangan ini, yang mengakibatkanunordered_map
memanggil konstruktor salinan lagi (aktiffoo8
) untuk membuat salinan internalnya sendiri (foo9
). Seperti denganfoo
0 dan 1, hasil akhirnya adalah dua panggilan destruktor untuk penyisipan ini dengan satu-satunya perbedaan adalah bahwafoo8
destruktor dipanggil hanya ketika kita mencapai akhirmain()
daripada dipanggil segera setelahinsert()
selesai.Emplacing
foo3
menghasilkan hanya 1 salinan / memindahkan panggilan konstruktor (membuatfoo10
internal diunordered_map
) dan hanya 1 panggilan keFoo
destruktor. (Saya akan kembali ke sini nanti).Karena
foo11
, kami langsung meneruskan bilangan bulat 11 keemplace(11, d)
sehinggaunordered_map
akan memanggilFoo(int)
konstruktor saat eksekusi dalamemplace()
metodenya. Tidak seperti pada (2) dan (3), kami bahkan tidak memerlukan objek yang sudah ada sebelumnyafoo
untuk melakukan ini. Yang penting, perhatikan bahwa hanya 1 panggilan keFoo
konstruktor terjadi (yang dibuatfoo11
).Kami kemudian langsung melewati bilangan bulat 12 ke
insert({12, d})
. Berbeda dengan denganemplace(11, d)
(yang mengingat hanya menghasilkan 1 panggilan keFoo
konstruktor), panggilan iniinsert({12, d})
menghasilkan dua panggilan keFoo
konstruktor (membuatfoo12
danfoo13
).Ini menunjukkan apa perbedaan "gambaran besar" utama antara
insert()
danemplace()
:Catatan: Alasan " hampir " di " hampir selalu " di atas dijelaskan pada I) di bawah ini.
umap.emplace(foo3, d)
disebutFoo
non-const copy constructor adalah sebagai berikut: Karena kita menggunakanemplace()
, compiler tahu bahwafoo3
(objek non-constFoo
) dimaksudkan untuk menjadi argumen untuk beberapaFoo
konstruktor. Dalam hal ini,Foo
konstruktor yang paling pas adalah konstruktor salinan non-constFoo(Foo& f2)
. Inilah sebabnya mengapaumap.emplace(foo3, d)
disebut copy constructor sementaraumap.emplace(11, d)
tidak.Epilog:
I. Perhatikan bahwa satu kelebihan
insert()
sebenarnya setara denganemplace()
. Seperti dijelaskan dalam halaman cppreference.com ini , kelebihantemplate<class P> std::pair<iterator, bool> insert(P&& value)
(yang kelebihan (2) dariinsert()
pada halaman cppreference.com ini) setara denganemplace(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 ) danemplace()
(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 bebaninsert()
atauemplace()
sedang dipanggil (dalam gerhana, pertahankan kursor mouse Anda stabil selama panggilan fungsi sebentar). Berikut beberapa kode untuk dicoba:Anda akan segera melihat bahwa kelebihan
std::pair
konstruktor (lihat referensi ) yang akhirnya digunakan olehunordered_map
dapat 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::set
ataustd::unordered_multiset
) sebagai gantinyastd::unordered_map
.c. Sekarang gunakan
Goo
objek (hanya salinan berganti nama dariFoo
) alih-alihint
sebagai jenis rentang dalamunordered_map
(yaitu menggunakanunordered_map<Foo, Goo>
alih-alihunordered_map<Foo, int>
) dan melihat berapa banyak dan yangGoo
disebut konstruktor. (Spoiler: ada efek tetapi tidak terlalu dramatis.)sumber
Dalam hal fungsi atau output, keduanya sama.
Untuk kedua memori besar, objek emplace dioptimalkan-memori yang tidak menggunakan copy constructor
Untuk penjelasan rinci sederhana, https://medium.com/@sandywits/all-about-emplace-in-c-71fd15e06e44
sumber