Hangi senaryoda belirli bir STL kapsayıcısı kullanırım?


186

C ++ ile ilgili kitabımda STL konteynırları, özellikle STL ve konteynırları bölümünde okudum. Şimdi her birinin kendine özgü özellikleri olduğunu anlıyorum ve hepsini ezberlemeye yakınım ... Ama henüz anlamadığım şey, her birinin hangi senaryoda kullanıldığıdır.

Açıklama nedir? Örnek kod çok tercih edilir.


Şunu mu demek istediniz: map, vectot, set etc
Thomas Tempelmann

Bu şemaya baktığımda bile, quastion'da kullanmak için en iyi olanın ne olacağını söyleyemem stackoverflow.com/questions/9329011/…
sergiol

2
@sbi: Bu etiketten C ++ SSS etiketi kaldırma ve daha yeni ve C ++ 11 dahil ekleme C + + 11 standart kitaplık kapsayıcısını nasıl verimli bir şekilde seçebilirim?
Alok

Yanıtlar:


338

Bu hile sayfası , farklı kapların oldukça iyi bir özetini sunar.

Farklı kullanım senaryolarında kullanılacak bir kılavuz olarak alttaki akış şemasına bakın:

http://linuxsoftware.co.nz/containerchoice.png

David Moore ve lisanslı CC BY-SA 3.0 tarafından oluşturulmuştur


14
Bu akış şeması altın, keşke c # böyle bir şey olsaydı
Bruno

2
Güncelleme bağlantısı: C ++ Kapsayıcıları Cheat Sheet .
Bill Door

3
Başlangıç ​​noktası vectorboş olmaktan ziyade boş olmalıdır . stackoverflow.com/questions/10699265/…
eonil

5
Artık unordered_mapve unordered_set(ve onların çoklu varyantları) akış şemasında değil, ancak düzeni umursamadığınızda ancak anahtarla öğeleri bulmanız gerektiğinde iyi seçimler var. Aramaları genellikle O (log n) yerine O (1) 'dir.
Aidiakapi

2
@ shuttle87 sadece bu boyut asla değişmez, daha da önemlisi bu boyut derleme zamanında belirlenir ve asla değişmez.
YoungJohn

188

İşte David Moore'un yarattığım versiyonundan (yukarıya bakınız) esinlenerek yeni standart (C ++ 11) ile güncel (çoğunlukla) bir akış şeması. Bu sadece benim kişisel görüşüm, tartışılmaz değil, ama bu tartışma için değerli olabileceğini düşündüm:

resim açıklamasını buraya girin


4
Orijinali kullanılabilir yapabilir misiniz? Mükemmel bir grafik. Belki bir bloga mı yoksa GitHub'a mı?
kevinarpe

1
Bu mükemmel bir grafik. Birisi bana 'kalıcı pozisyonlar' ile ne kastedildiğini açıklayabilir mi?
IDDQD

3
@STALKER Kalıcı konumlar, kaptaki bir öğeye işaretçi veya yineleyici varsa, işaretçi veya yineleyicinin kaptan ne eklediğiniz veya kaldırdığınızdan bağımsız olarak (ve aynı öğeyi işaret ettiği) geçerli kalacağı anlamına gelir. söz konusu öğe değildir).
Mikael Persson

1
Bu gerçekten harika bir grafik, ancak vector (sorted)geri kalanıyla biraz tutarsız olduğunu düşünüyorum . Farklı bir konteyner türü değil, aynıdır std::vectorancak sıralanmıştır. Daha da önemlisi, std::setbir küme üzerinden yineleme standart davranışı buysa, neden bir sıralı yineleme için kullanamadığımı görmüyorum . Elbette, cevap kap oluğunun değerlerine düzenli olarak erişmekten bahsediyorsa [], o zaman bunu sadece bir sıralı ile yapabilirsiniz std::vector. Ancak her iki durumda da, karar "sipariş gerekli" sorusunun hemen ardından verilmelidir
RAs

1
@ user2019840 Grafiği standart kaplarla sınırlamak istedim. "Sıralı vektör" yerine ne görünmesi gerektiği "flat_set" ( Boost.Container'dan ) veya eşdeğerdir (her büyük kütüphane veya kod tabanının flat_set eşdeğeri AFAIK vardır). Ancak bunlar standart değildir ve STL'den oldukça göze çarpan bir ihmaldir. Ve std :: set veya std :: map (en azından sık sık değil) ile tekrarlamak istememenizin nedeni, bunu yapmanın çok verimsiz olmasıdır .
Mikael Persson

41

Basit cevap: std::vectoraksi takdirde gerçek bir nedeniniz yoksa her şey için kullanın .

"Gee, std::vectorX yüzünden burada iyi çalışmıyor " diye düşündüğünüz bir durum bulduğunuzda , X'e göre gidin.


1
Ancak .. yineleme sırasında öğeleri silmek / eklemek için dikkatli olun ... bunu önlemek için mümkün olduğunca const_iterator kullanın ..
vrdhn

11
Hmm ... Bence insanlar fazla vektör kullanıyorlar. Bunun nedeni, "işe yaramaz" kutusunun kolayca gerçekleşmeyeceğidir - bu yüzden insanlar en sık kullanılan kapsayıcıya yapışır ve listeleri, kuyrukları, depolamak için kötüye kullanır ... Bence - akış şemasına uyan - kişi "herkese uygun görünüyor" yerine kullanım amacına göre kabı seçmelidir.
Siyah

13
@Siyah nokta, teoride daha yavaş çalışması gereken operasyonlarda bile vektör genellikle daha hızlıdır.
Bartek Banachewicz

1
@Vardhan std::remove_ifneredeyse her zaman "yineleme sırasında sil" yaklaşımından üstündür.
fredoverflow

1
Bazı kriterler bu tartışmanın daha az öznel olmasına yardımcı olacaktır.
Felix D.

11

Scott Meyers'ın Etkili STL'sine bakın. STL'nin nasıl kullanılacağını açıklamak iyidir.

Belirlenmiş / belirlenmemiş sayıda nesne saklamak istiyorsanız ve hiçbir zaman hiçbirini silmeyecekseniz, bir vektör istediğiniz şeydir. Bu bir C dizisinin varsayılan yedeğidir ve bir gibi çalışır, ancak taşmaz. Boyutunu önceden rezerv () ile de ayarlayabilirsiniz.

Belirsiz sayıda nesneyi saklamak istiyorsanız, ancak bunları ekleyip sileceksiniz, o zaman muhtemelen bir liste istiyorsunuz ... çünkü vektörden farklı olarak aşağıdaki öğeleri taşımadan bir öğeyi silebilirsiniz. Bununla birlikte, bir vektörden daha fazla bellek alır ve bir öğeye sırayla erişemezsiniz.

Bir grup öğeyi almak ve bu öğelerin yalnızca benzersiz değerlerini bulmak istiyorsanız, hepsini bir set halinde okumak bunu yapar ve sizin için de sıralar.

Çok sayıda anahtar / değer çiftiniz varsa ve bunları anahtarla sıralamak istiyorsanız, bir harita yararlı olur ... ancak anahtar başına yalnızca bir değer tutacaktır. Anahtar başına birden fazla değere ihtiyacınız varsa, haritadaki değeriniz olarak bir vektörünüz / listeniz olabilir veya bir çoklu harita kullanabilirsiniz.

STL'de değil, ancak STL'nin TR1 güncellemesinde: anahtarla arayacağınız çok sayıda anahtar / değer çiftiniz varsa ve bunların sırasını umursamıyorsanız, hash kullanmak istiyorum - ki bu tr1 :: unordered_map. Ben stdext :: hash_map denilen Visual C ++ 7.1 ile kullandım. Harita için O (log n) yerine O (1) aramasına sahiptir.


Microsoft'un hash_mapçok iyi bir uygulama olmadığını öne süren birkaç fıkra duydum . Umarım daha iyisini yapmışlardır unordered_map.
Mark Ransom

3
Listelerden - "bir öğeye sırayla erişemezsiniz." - Sanırım doğrudan bir öğeye rastgele erişemez veya endeksleyemezsiniz ....
Tony Delroy

^ Evet, çünkü sıralı erişim tam olarak a'nın yaptığı şeydir list. Oldukça göze batan hata var.
underscore_d

7

Akış şemasını 3 özelliğe sahip olacak şekilde yeniden tasarladım:

  1. Bence STL kapları 2 ana sınıfa ayrılmıştır. Temel kapsayıcılar ve bunlar bir ilke uygulamak için temel kapsayıcılardan yararlanır.
  2. İlk önce akış şeması, karar sürecini karar vermemiz gereken ana durumlara ayırmalı ve daha sonra her davada detaylandırmalıyız.
  3. Bazı uzatılmış kaplar, iç kapları olarak farklı temel kapları seçme olanağına sahiptir. Akış Şeması, temel kapların her birinin kullanılabileceği durumları dikkate almalıdır.

Akış şeması: resim açıklamasını buraya girin

Bu bağlantıda daha fazla bilgi verilmiştir .


5

Kısaca şu ana kadar bahsedilen önemli bir nokta, (C dizisi verir gibi) bitişik bellek gerektiriyorsa, o zaman sadece kullanabilirsiniz olmasıdır vector, arrayya da string.

arrayBoyut derleme zamanında biliniyorsa kullanın .

stringYalnızca genel amaçlı bir kapsayıcıya değil, yalnızca karakter türleriyle çalışmanız ve bir dizeye ihtiyacınız varsa kullanın .

vectorDiğer tüm durumlarda kullanın ( vectoryine de çoğu durumda varsayılan konteyner seçimi olmalıdır).

Bunların üçünde data()de, kabın ilk öğesine bir işaretçi almak için üye işlevini kullanabilirsiniz .


3

Her şey neyi saklamak istediğinize ve konteynerle ne yapmak istediğinize bağlıdır. En çok kullanma eğiliminde olduğum konteyner sınıfları için bazı (çok ayrıntılı olmayan) örnekler:

vector: Her bir nesne için bellek yükü çok az veya hiç yokken kompakt düzen. Tekrarlamada etkilidir. Ekleme, ekleme ve silme, özellikle karmaşık nesneler için pahalı olabilir. İçerdiği bir nesneyi indekse göre bulmak ucuz, örneğin myVector [10]. C'de bir dizi kullanacağınız yeri kullanın. Çok sayıda basit nesneye sahip olduğunuz yer (örn. İnt). reserve()Konteynere çok fazla nesne eklemeden önce kullanmayı unutmayın .

list: İçerilen nesne başına küçük bellek yükü. Tekrarlamada etkilidir. Ekleme, ekleme ve silme ucuzdur. C'de bağlantılı bir liste kullanacağınız yeri kullanın.

set(ve multiset): İçerilen nesne başına önemli bellek yükü. Bu kabın belirli bir nesne içerip içermediğini hızlıca öğrenmeniz gereken yerlerde kullanın veya kapları verimli bir şekilde birleştirin.

map(ve multimap): İçerilen nesne başına önemli bellek yükü. Anahtar / değer çiftlerini saklamak istediğiniz yeri kullanın ve değerleri anahtarla hızlı bir şekilde arayın.

Zdan tarafından önerilen hile sayfasındaki akış şeması daha ayrıntılı bir kılavuz sağlar.


"İçerilen nesne başına küçük bellek ek yükü" liste için geçerli değil. std :: list, iki kez bağlantılı liste olarak uygulanır ve saklanan her nesne için 2 işaretçi bulundurur, bu da ihmal edilmez.
Hanna Khalil

Saklanan nesne başına iki işaretçi "küçük" olarak saymak.
Teklifler

Neyle karşılaştırılmış? std :: forward_list, temel olarak nesne başına daha az meta veri depolaması önerilen bir kapsayıcıdır (yalnızca bir işaretçi). Std :: vector nesne başına 0 meta veri tutarken. Yani 2 konteynır diğer konteynırlarla kıyaslanamaz
Hanna Khalil

Her şey nesnelerinizin boyutuna bağlıdır. Ben zaten vektörün "içerilen nesne başına bellek yükünün çok az veya hiç olmadığı kompakt bir yerleşim düzeni" olduğunu söyledim. Hala liste ve harita ile karşılaştırıldığında küçük bir bellek yükü ve vektörden biraz daha büyük bir bellek yükü olduğunu söyleyebilirim. TBH'yi hangi noktaya yapmaya çalıştığınızdan gerçekten emin değilim!
Teklifler

Tüm mod tabanlı kaplar, nadiren ücretsiz olarak gelen dinamik ayırma nedeniyle önemli ölçüde ek yüke sahip olma eğilimindedir. Tabii ki özel bir ayırıcı kullanmıyorsanız.
MikeMB

2

Öğrendiğim bir ders: Bir sınıfa sarmaya çalışın, çünkü konteyner tipini bir gün değiştirmek büyük sürprizler getirebilir.

class CollectionOfFoo {
    Collection<Foo*> foos;
    .. delegate methods specifically 
}

Çok pahalıya mal olmaz ve birisi bu yapı üzerinde x işlemi yaptığında kırılmak istediğinizde hata ayıklamada zaman kazandırır.

Bir iş için mükemmel veri yapısını seçmeye geliyor:

Her veri yapısı, zaman karmaşıklığını değiştirebilecek bazı işlemler sağlar:

O (1), O (lg N), O (N) vb.

Esasen, en çok hangi işlemlerin yapılacağını en iyi tahmin etmeniz ve bu işlemi O (1) olarak içeren bir veri yapısı kullanmanız gerekir.

Basit, değil mi (-:


5
Yineleyicileri kullanmamızın nedeni bu değil mi?
Platinum Azure

@PlatinumAzure Yineleyiciler bile üye typedef olmalı .. Konteyner türünü değiştirirseniz, tüm yineleyici tanımlarını da gidip değiştirmelisiniz ...
vrdhn

4
Meraklı biri için, bu C ++ 11'deki düzeltmedir: auto myIterator = whateverCollection.begin(); // <-- immune to changes of container type
Siyah

1
A typedef Collection<Foo*> CollectionOfFoo;yeterli olur mu?
Craig McQueen

5
Daha sonra fikrinizi değiştirip sadece farklı bir kaba devredebilmeniz pek olası değildir:
Kaptan


1

Bunu, bunun bir kopyası olarak işaretlenmiş başka bir soruda yanıtladı. Ancak standart bir konteyner seçme kararıyla ilgili bazı iyi makalelere başvurmanın güzel olduğunu hissediyorum.

@David Thornley'nin yanıtladığı gibi, std :: vector başka özel ihtiyaçlar yoksa gitmenin yoludur. Bu, 2014 blogunda C ++ yaratıcısı Bjarne Stroustrup tarafından verilen tavsiyedir.

İşte https://isocpp.org/blog/2014/06/stroustrup-lists makalesinin bağlantısı

ve bundan alıntı yapın,

Ve evet, benim önerim varsayılan olarak std :: vector kullanmaktır.

Yorumlarda, @NathanOliver kullanıcısı, daha somut ölçümlere sahip başka bir iyi blog sunuyor. https://baptiste-wicht.com/posts/2012/12/cpp-benchmark-vector-list-deque.html .

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.