Saya telah mengidentifikasi empat cara berbeda untuk memasukkan elemen ke dalam std::map
:
std::map<int, int> function;
function[0] = 42;
function.insert(std::map<int, int>::value_type(0, 42));
function.insert(std::pair<int, int>(0, 42));
function.insert(std::make_pair(0, 42));
Manakah dari itu yang lebih disukai / cara idiomatik? (Dan apakah ada cara lain yang belum saya pikirkan?)
Jawaban:
Pertama-tama,
operator[]
daninsert
fungsi anggota tidak setara secara fungsional:operator[]
akan mencari kunci, memasukkan nilai bawaan yang dibangun jika tidak ditemukan, dan mengembalikan referensi yang Anda tetapkan nilai. Jelas, ini bisa menjadi tidak efisien jikamapped_type
bisa mendapatkan keuntungan dari langsung diinisialisasi daripada default dibangun dan ditetapkan. Metode ini juga tidak memungkinkan untuk menentukan apakah penyisipan benar-benar terjadi atau jika Anda hanya menimpa nilai untuk kunci yang dimasukkan sebelumnyainsert
anggota tidak akan berpengaruh jika kunci sudah ada di peta dan, meskipun sering dilupakan, mengembalikanstd::pair<iterator, bool>
mana yang menarik (terutama untuk menentukan apakah penyisipan benar-benar telah dilakukan).Dari semua kemungkinan yang terdaftar untuk menelepon
insert
, ketiganya hampir setara. Sebagai pengingat, mari kita lihatinsert
tanda tangan dalam standar:Jadi, bagaimana ketiga panggilan itu berbeda?
std::make_pair
bergantung pada pengurangan argumen template dan dapat (dan dalam hal ini akan ) menghasilkan sesuatu dengan tipe yang berbeda dari yang sebenarnyavalue_type
dari peta, yang akan membutuhkan panggilan tambahan kestd::pair
konstruktor template untuk mengonversi kevalue_type
(yaitu: menambahkanconst
kefirst_type
)std::pair<int, int>
juga akan membutuhkan panggilan tambahan ke konstruktor templatestd::pair
untuk mengubah parameter menjadivalue_type
(yaitu: menambahkanconst
kefirst_type
)std::map<int, int>::value_type
sama sekali tidak menyisakan tempat untuk keraguan karena secara langsung tipe parameter yang diharapkan olehinsert
fungsi anggota.Pada akhirnya, saya akan menghindari penggunaan
operator[]
jika tujuannya adalah untuk memasukkan, kecuali jika tidak ada biaya tambahan dalam membangun dan menetapkan secara defaultmapped_type
, dan bahwa saya tidak peduli tentang menentukan apakah kunci baru telah dimasukkan secara efektif. Saat menggunakaninsert
, membuat avalue_type
mungkin adalah cara yang tepat.sumber
Mulai C ++ 11, Anda memiliki dua opsi tambahan utama. Pertama, Anda dapat menggunakan
insert()
sintaks inisialisasi dengan daftar:Ini secara fungsional setara dengan
tetapi jauh lebih ringkas dan mudah dibaca. Seperti jawaban lain telah dicatat, ini memiliki beberapa keunggulan dibandingkan bentuk lain:
operator[]
Pendekatan membutuhkan jenis dipetakan menjadi dialihkan, yang tidak selalu terjadi.operator[]
pendekatan dapat menimpa elemen yang ada, dan memberikan Anda ada cara untuk mengetahui apakah hal ini terjadi.insert
yang Anda daftarkan melibatkan jenis konversi implisit, yang dapat memperlambat kode Anda.Kelemahan utama adalah bahwa formulir ini biasanya memerlukan kunci dan nilai agar dapat disalin, sehingga tidak akan berfungsi dengan misalnya peta dengan
unique_ptr
nilai. Itu telah diperbaiki dalam standar, tetapi perbaikannya mungkin belum mencapai implementasi perpustakaan standar Anda.Kedua, Anda dapat menggunakan
emplace()
metode ini:Ini lebih ringkas daripada bentuk apapun
insert()
, bekerja dengan baik dengan tipe yang hanya bergerak sepertiunique_ptr
, dan secara teoritis mungkin sedikit lebih efisien (walaupun kompiler yang layak harus mengoptimalkan perbedaannya). Satu-satunya kelemahan utama adalah ini mungkin sedikit mengejutkan pembaca Anda, karenaemplace
metode biasanya tidak digunakan seperti itu.sumber
Versi pertama:
mungkin atau mungkin tidak memasukkan nilai 42 ke dalam peta. Jika kunci tersebut
0
ada, maka kunci tersebut akan menetapkan 42 ke kunci itu, menimpa nilai apa pun yang dimiliki kunci itu. Jika tidak, itu akan memasukkan pasangan kunci / nilai.Fungsi sisipan:
di sisi lain, jangan lakukan apa pun jika kunci
0
sudah ada di peta. Jika kunci tidak ada, itu akan memasukkan pasangan kunci / nilai.Ketiga fungsi sisipan hampir identik.
std::map<int, int>::value_type
adalahtypedef
untukstd::pair<const int, int>
, danstd::make_pair()
jelas menghasilkanstd::pair<>
sihir deduksi melalui template. Namun, hasil akhirnya harus sama untuk versi 2, 3, dan 4.Yang mana yang akan saya gunakan? Saya pribadi lebih suka versi 1; itu ringkas dan "alami". Tentu saja, jika perilaku penimpaannya tidak diinginkan, maka saya lebih suka versi 4, karena ini membutuhkan lebih sedikit pengetikan daripada versi 2 dan 3. Saya tidak tahu apakah ada satu cara de facto untuk memasukkan pasangan kunci / nilai ke dalam
std::map
.Cara lain untuk memasukkan nilai ke dalam peta melalui salah satu konstruktornya:
sumber
Jika Anda ingin menimpa elemen dengan kunci 0
Jika tidak:
sumber
Karena C ++ 17
std::map
menawarkan dua metode penyisipan baru:insert_or_assign()
dantry_emplace()
, seperti juga disebutkan dalam komentar oleh sp2danny .insert_or_assign()
Pada dasarnya,
insert_or_assign()
ini adalah versi yang "ditingkatkan" darioperator[]
. Berbeda denganoperator[]
,insert_or_assign()
tidak mengharuskan tipe nilai peta menjadi dapat dibangun secara default. Misalnya, kode berikut tidak dapat dikompilasi, karenaMyClass
tidak memiliki konstruktor default:Namun, jika Anda mengganti
myMap[0] = MyClass(1);
dengan baris berikut, maka kode akan terkompilasi dan penyisipan berlangsung seperti yang diinginkan:Selain itu, mirip dengan
insert()
,insert_or_assign()
mengembalikan apair<iterator, bool>
. Nilai Boolean adalahtrue
jika penyisipan terjadi danfalse
jika tugas telah selesai. Iterator menunjuk ke elemen yang disisipkan atau diperbarui.try_emplace()
Mirip dengan di atas,
try_emplace()
merupakan "perbaikan" dariemplace()
. Berbeda denganemplace()
,try_emplace()
tidak mengubah argumennya jika penyisipan gagal karena kunci sudah ada di peta. Misalnya, kode berikut mencoba untuk menempatkan elemen dengan kunci yang sudah disimpan di peta (lihat *):Output (setidaknya untuk VS2017 dan Coliru):
Seperti yang Anda lihat,
pMyObj
tidak lagi menunjuk ke objek aslinya. Namun, jika Anda menggantiauto [it, b] = myMap2.emplace(0, std::move(pMyObj));
dengan kode berikut, maka keluarannya terlihat berbeda, karenapMyObj
tetap tidak berubah:Keluaran:
Kode di Coliru
Harap diperhatikan: Saya mencoba membuat penjelasan saya sesingkat dan sesederhana mungkin agar sesuai dengan jawaban ini. Untuk penjelasan yang lebih tepat dan komprehensif, saya sarankan membaca artikel tentang Fluent C ++ ini .
sumber
Saya telah menjalankan beberapa perbandingan waktu antara versi yang disebutkan di atas:
Ternyata perbedaan waktu antara versi sisipan sangat kecil.
Ini memberikan masing-masing untuk versi (saya menjalankan file 3 kali, maka 3 perbedaan waktu berturut-turut untuk masing-masing):
2198 md, 2078 md, 2072 md
2290 md, 2037 md, 2046 md
2592 ms, 2278 ms, 2296 ms
2234 md, 2031 md, 2027 md
Oleh karena itu, hasil antara versi sisipan yang berbeda dapat diabaikan (meskipun tidak melakukan uji hipotesis)!
The
map_W_3[it] = Widget(2.0);
Versi memakan waktu sekitar 10-15% lebih banyak waktu untuk contoh ini disebabkan oleh inisialisasi dengan konstruktor default untuk Widget.sumber
Singkatnya,
[]
operator lebih efisien untuk memperbarui nilai karena melibatkan pemanggilan konstruktor default dari tipe nilai dan kemudian menetapkan nilai baru, sementarainsert()
lebih efisien untuk menambahkan nilai.Potongan kutipan dari STL Efektif: 50 Cara Khusus untuk Meningkatkan Penggunaan Anda dari Perpustakaan Templat Standar oleh Scott Meyers, Butir 24 mungkin membantu.
Anda dapat memutuskan untuk memilih versi bebas-pemrograman-generik ini, tetapi intinya adalah saya menemukan paradigma ini (membedakan 'tambah' dan 'perbarui') sangat berguna.
sumber
Jika Anda ingin menyisipkan elemen dalam fungsi std :: map - gunakan insert (), dan jika Anda ingin mencari elemen (dengan kunci) dan menetapkan beberapa ke dalamnya - gunakan operator [].
Untuk menyederhanakan penyisipan gunakan pustaka boost :: assign, seperti ini:
sumber
Saya hanya mengubah masalah sedikit (peta string) untuk menunjukkan minat lain untuk memasukkan:
fakta bahwa kompilator tidak menunjukkan kesalahan pada "rancking [1] = 42;" dapat memiliki dampak yang menghancurkan!
sumber
std::string::operator=(char)
ada, tetapi mereka menunjukkan kesalahan untuk yang terakhir karena konstruktornyastd::string::string(char)
tidak ada. Ini seharusnya tidak menghasilkan kesalahan karena C ++ selalu bebas menafsirkan setiap bilangan bulat-gaya literal sebagaichar
, jadi ini bukan bug compiler, tetapi merupakan kesalahan programmer. Pada dasarnya, saya hanya mengatakan bahwa apakah itu memperkenalkan bug dalam kode Anda atau tidak adalah sesuatu yang harus Anda perhatikan sendiri. BTW, Anda dapat mencetakrancking[0]
dan kompiler menggunakan ASCII akan mengeluarkan*
, yaitu(char)(42)
.