Ada gambar terkenal (lembar contekan) yang disebut "C ++ Container choice". Ini adalah diagram alir untuk memilih penampung terbaik untuk penggunaan yang diinginkan.
Adakah yang tahu jika sudah ada versi C ++ 11?
Ini yang sebelumnya:
Ada gambar terkenal (lembar contekan) yang disebut "C ++ Container choice". Ini adalah diagram alir untuk memilih penampung terbaik untuk penggunaan yang diinginkan.
Adakah yang tahu jika sudah ada versi C ++ 11?
Ini yang sebelumnya:
Jawaban:
Setahu saya tidak, namun itu bisa dilakukan secara tekstual. Juga, bagannya sedikit melenceng, karena list
bukan merupakan wadah yang baik secara umum, begitu pula halnya forward_list
. Kedua daftar tersebut adalah wadah yang sangat khusus untuk aplikasi khusus.
Untuk membuat bagan seperti itu, Anda hanya perlu dua pedoman sederhana:
Khawatir tentang kinerja biasanya tidak berguna pada awalnya. Pertimbangan O besar hanya benar-benar muncul ketika Anda mulai menangani beberapa ribu (atau lebih) item.
Ada dua kategori besar wadah:
find
operasidan kemudian Anda dapat membangun beberapa adapter di atas mereka: stack
, queue
, priority_queue
. Saya akan meninggalkan adaptornya di sini, mereka cukup terspesialisasi agar dapat dikenali.
Pertanyaan 1: Asosiatif ?
Pertanyaan 1.1: Dipesan ?
unordered_
wadah, jika tidak gunakan pesanan pesanan tradisionalnya.Pertanyaan 1.2: Kunci Terpisah ?
map
, jika tidak gunakan aset
Pertanyaan 1.3: Duplikat ?
multi
, jika tidak, jangan.Contoh:
Misalkan saya memiliki beberapa orang dengan ID unik yang terkait dengannya, dan saya ingin mengambil data seseorang dari ID-nya sesederhana mungkin.
Saya ingin sebuah find
fungsi, jadi wadah asosiatif
1.1. Saya tidak peduli tentang pesanan, jadi sebuah unordered_
wadah
1.2. Kunci saya (ID) terpisah dari nilai yang diasosiasikan dengannya, jadi amap
1.3. ID-nya unik, jadi tidak ada duplikat yang masuk.
Jawaban akhir adalah: std::unordered_map<ID, PersonData>
.
Pertanyaan 2: Memori stabil ?
list
Pertanyaan 2.1: Yang mana ?
list
; a forward_list
hanya berguna untuk footprint memori yang lebih rendah.Pertanyaan 3: Berukuran dinamis ?
{ ... }
sintaks), lalu gunakan array
. Ini menggantikan C-array tradisional, tetapi dengan fungsi yang nyaman.Pertanyaan 4: Berujung ganda ?
deque
, jika tidak gunakan a vector
.Anda akan mencatat bahwa, secara default, kecuali Anda membutuhkan wadah asosiatif, pilihan Anda adalah a vector
. Ternyata itu juga rekomendasi Sutter dan Stroustrup .
array
tidak memerlukan tipe konstruktif default; 2) memilih multi
s tidak terlalu banyak tentang duplikat yang diizinkan tetapi lebih tentang apakah menyimpannya penting (Anda dapat meletakkan duplikat di non- multi
container, kebetulan hanya satu yang disimpan).
map.find(key)
jauh lebih enak dari pada std::find(map.begin(), map.end(), [&](decltype(map.front()) p) { return p.first < key; }));
meskipun, oleh karena itu penting, secara semantik, itu find
adalah fungsi anggota dan bukan dari <algorithm>
. Adapun O (1) vs O (log n), itu tidak mempengaruhi semantik; Saya akan menghapus "efisien" dari contoh dan menggantinya dengan "dengan mudah".
deque
punya properti ini juga?
deque
elemen stabil hanya jika Anda menekan / pop di kedua ujungnya; jika Anda mulai memasukkan / menghapus di tengah maka hingga N / 2 elemen dikocok untuk mengisi celah yang dibuat.
Saya suka jawaban Matthieu, tetapi saya akan mengulangi diagram alurnya seperti ini:
Secara default, jika Anda membutuhkan wadah barang, gunakan std::vector
. Dengan demikian, setiap penampung lain hanya dibenarkan dengan menyediakan beberapa alternatif fungsionalitas std::vector
.
std::vector
mengharuskan isinya dapat dipindahkan, karena harus dapat mengocok item di sekitar. Ini bukan beban yang berat untuk ditempatkan pada konten (perhatikan bahwa konstruktor default tidak diperlukan , berkat emplace
dan sebagainya). Namun, sebagian besar wadah lain tidak memerlukan konstruktor tertentu (sekali lagi, terima kasih emplace
). Jadi jika Anda memiliki objek di mana Anda sama sekali tidak dapat mengimplementasikan konstruktor pemindahan, maka Anda harus memilih yang lain.
A std::deque
akan menjadi pengganti umum, memiliki banyak properti std::vector
, tetapi Anda hanya dapat memasukkan di kedua ujung deque. Sisipan di tengah perlu dipindahkan. A std::list
tidak mensyaratkan isinya.
std::vector<bool>
tidak. Ya, ini standar. Tapi itu bukan vector
dalam arti biasa, karena operasi yang std::vector
biasanya memungkinkan dilarang. Dan itu pasti tidak mengandung bool
s .
Oleh karena itu, jika Anda memerlukan vector
perilaku nyata dari wadah bool
s, Anda tidak akan mendapatkannya std::vector<bool>
. Jadi, Anda harus membayar dengan a std::deque<bool>
.
Jika Anda perlu menemukan elemen dalam sebuah wadah, dan tag pencarian tidak bisa hanya berupa indeks, maka Anda mungkin perlu meninggalkannya std::vector
demi set
dan map
. Perhatikan kata kunci " mungkin "; penyortiran std::vector
terkadang merupakan alternatif yang masuk akal. Atau Boost.Container's flat_set/map
, yang mengimplementasikan file std::vector
.
Sekarang ada empat variasi, masing-masing dengan kebutuhannya sendiri.
map
jika tag pencarian tidak sama dengan item yang Anda cari sendiri. Jika tidak, gunakan a set
.unordered
jika Anda memiliki banyak item dalam penampung dan kinerja penelusuran benar-benar perlu O(1)
, bukan O(logn)
.multi
jika Anda membutuhkan banyak item untuk memiliki tag pencarian yang sama.Jika Anda membutuhkan penampung item untuk selalu diurutkan berdasarkan operasi perbandingan tertentu, Anda dapat menggunakan file set
. Atau multi_set
jika Anda membutuhkan beberapa item untuk memiliki nilai yang sama.
Atau Anda dapat menggunakan penyortiran std::vector
, tetapi Anda harus tetap menyortirnya.
Ketika iterator dan referensi tidak valid terkadang menjadi perhatian. Jika Anda memerlukan daftar item, sehingga Anda memiliki iterator / penunjuk ke item tersebut di berbagai tempat lain, maka std::vector
pendekatan pembatalan mungkin tidak tepat. Setiap operasi penyisipan dapat menyebabkan pembatalan, tergantung pada ukuran dan kapasitas saat ini.
std::list
menawarkan jaminan pasti: iterator dan referensi / petunjuk terkaitnya hanya tidak valid saat item itu sendiri dikeluarkan dari wadah. std::forward_list
apakah ada jika ingatan menjadi perhatian serius.
Jika itu jaminan yang terlalu kuat, std::deque
tawarkan jaminan yang lebih lemah tapi berguna. Hasil invalidasi dari penyisipan di tengah, tetapi penyisipan di kepala atau ekor hanya menyebabkan pembatalan iterator , bukan penunjuk / referensi ke item dalam penampung.
std::vector
hanya menyediakan penyisipan murah di akhir (dan bahkan kemudian, menjadi mahal jika Anda meniup kapasitas).
std::list
mahal dalam hal performa (setiap item yang baru dimasukkan memerlukan alokasi memori), tetapi konsisten . Ini juga menawarkan kemampuan yang kadang-kadang sangat diperlukan untuk mengocok barang-barang dengan hampir tanpa biaya kinerja, serta untuk menukar barang dengan std::list
wadah lain dari jenis yang sama tanpa kehilangan kinerja. Jika Anda perlu untuk mengocok hal-hal di sekitar banyak , penggunaan std::list
.
std::deque
menyediakan penyisipan / pemindahan waktu konstan di kepala dan ekor, tetapi penyisipan di tengah bisa cukup mahal. Jadi jika Anda perlu menambah / menghapus barang dari depan maupun belakang, std::deque
mungkin yang Anda butuhkan.
Perlu dicatat bahwa, berkat semantik pemindahan, std::vector
kinerja penyisipan mungkin tidak seburuk dulu. Beberapa implementasi menerapkan bentuk penyalinan item berbasis semantik pemindahan (yang disebut "swaptimization"), tetapi sekarang pemindahan adalah bagian dari bahasa, itu diamanatkan oleh standar.
std::array
adalah wadah yang bagus jika Anda menginginkan alokasi dinamis sesedikit mungkin. Ini hanya pembungkus di sekitar C-array; ini berarti ukurannya harus diketahui pada saat kompilasi . Jika Anda bisa hidup dengan itu, maka gunakan std::array
.
Karena itu, menggunakan std::vector
dan reserve
mengukur ukuran akan bekerja dengan baik untuk yang dibatasi std::vector
. Dengan cara ini, ukuran sebenarnya dapat bervariasi, dan Anda hanya mendapatkan satu alokasi memori (kecuali jika kapasitasnya habis).
std::sort
, ada juga std::inplace_merge
yang menarik untuk dengan mudah menempatkan elemen baru (daripada panggilan std::lower_bound
+ std::vector::insert
). Senang belajar tentang flat_set
dan flat_map
!
vector<bool>
itu vector<char>
.
std::allocator<T>
tidak mendukung perataan itu (dan saya tidak tahu mengapa tidak), maka Anda selalu dapat menggunakan pengalokasi khusus Anda sendiri.
std::vector::resize
memiliki kelebihan beban yang tidak mengambil nilai (hanya mengambil ukuran baru; elemen baru akan dibuat secara default di tempat). Juga, mengapa kompiler tidak dapat menyelaraskan parameter nilai dengan benar, bahkan ketika mereka dinyatakan memiliki keselarasan itu?
bitset
untuk bool jika Anda mengetahui ukurannya terlebih dahulu en.cppreference.com/w/cpp/utility/bitset
Berikut adalah versi C ++ 11 dari diagram alur di atas. [awalnya diposting tanpa atribusi ke penulis aslinya, Mikael Persson ]
Ini putaran cepat, meskipun mungkin perlu diperbaiki
Should the container let you manage the order of the elements?
Yes:
Will the container contain always exactly the same number of elements?
Yes:
Does the container need a fast move operator?
Yes: std::vector
No: std::array
No:
Do you absolutely need stable iterators? (be certain!)
Yes: boost::stable_vector (as a last case fallback, std::list)
No:
Do inserts happen only at the ends?
Yes: std::deque
No: std::vector
No:
Are keys associated with Values?
Yes:
Do the keys need to be sorted?
Yes:
Are there more than one value per key?
Yes: boost::flat_map (as a last case fallback, std::map)
No: boost::flat_multimap (as a last case fallback, std::map)
No:
Are there more than one value per key?
Yes: std::unordered_multimap
No: std::unordered_map
No:
Are elements read then removed in a certain order?
Yes:
Order is:
Ordered by element: std::priority_queue
First in First out: std::queue
First in Last out: std::stack
Other: Custom based on std::vector?????
No:
Should the elements be sorted by value?
Yes: boost::flat_set
No: std::vector
Anda mungkin memperhatikan bahwa ini sangat berbeda dari versi C ++ 03, terutama karena fakta bahwa saya benar-benar tidak menyukai node yang terhubung. Penampung node yang ditautkan biasanya dapat dikalahkan kinerjanya oleh penampung yang tidak tertaut, kecuali dalam beberapa situasi yang jarang terjadi. Jika Anda tidak tahu apa situasi tersebut, dan memiliki akses untuk meningkatkan, jangan gunakan wadah node tertaut. (std :: list, std :: slist, std :: map, std :: multimap, std :: set, std :: multiset). Daftar ini sebagian besar berfokus pada penampung sisi kecil dan menengah, karena (A) itu 99,99% dari apa yang kita tangani dalam kode, dan (B) Sejumlah besar elemen membutuhkan algoritme khusus, bukan penampung yang berbeda.