C ++ 11'de bir Standart Kitaplık kapsayıcısını nasıl verimli bir şekilde seçebilirim?


135

"C ++ Kapsayıcı seçimi" adı verilen iyi bilinen bir görüntü (kopya kağıdı) var. İstenen kullanım için en iyi kabı seçmek için bir akış şemasıdır.

Zaten bir C ++ 11 sürümü olup olmadığını bilen var mı?

Bu bir öncekidir: eC ++ Kapsayıcı seçimi


6
Bunu daha önce hiç görmedim. Teşekkürler!
WeaselFox

6
@WeaselFox: Zaten SO üzerinde C ++ - SSS bir parçası .
Alok

4
C ++ 11 yalnızca bir yeni doğru kapsayıcı türü tanıttı: unordered_X kapsayıcılar. Bir karma tablonun uygun olup olmadığına karar verirken bir dizi dikkate alınması gerektiğinden, bunları dahil etmek tabloyu sadece önemli ölçüde karıştırır.
Nicol Bolas

13
James haklı, vektörü kullanmak tablonun gösterdiklerinden daha fazla vaka var. Veri yerinin avantajı, çoğu durumda, bazı işlemlerde verimlilik eksikliğinden daha iyi performans gösterir (daha yakında C ++ 11). Ben e-grafik bile c ++ 03 için çok yararlı bulmuyorum
David Rodríguez - dribeas

33
Bu sevimli, ama veri yapıları üzerinde herhangi bir ortak ders kitabı okumak sadece birkaç dakika içinde bu akış şemasını yeniden icat değil, aynı zamanda bu akış şemasının üzerinde parlıyor çok daha yararlı şeyler biliyorum bir durumda bırakacağını düşünüyorum.
Andrew Tomazos

Yanıtlar:


97

Bildiğimden değil, ancak sanırım metinsel olarak yapılabilir. Ayrıca, grafik biraz kapalıdır, çünkü listgenel olarak böyle iyi bir kap değildir ve ikisi de değildir forward_list. Her iki liste de niş uygulamalar için çok özel kaplardır.

Böyle bir grafik oluşturmak için sadece iki basit yönerge gerekir:

  • Önce anlam bilgisi için seçin
  • Birkaç seçenek mevcut olduğunda, en basit olanı tercih edin.

Performanstan endişe etmek genellikle ilk başta işe yaramaz. Büyük O düşünceleri sadece birkaç binlerce (veya daha fazla) öğeyi işlemeye başladığınızda ortaya çıkar.

İki büyük konteyner kategorisi vardır:

  • İlişkili konteynerler: bir findoperasyonları var
  • Basit Dizi kapları

ve sonra bunların üstüne birkaç adaptörleri inşa edebilirsiniz: stack, queue, priority_queue. Adaptörleri burada bırakacağım, tanınabilecek kadar uzmanlar.


Soru 1: İlişkisel ?

  • Tek bir tuşla kolayca arama yapmanız gerekiyorsa, ilişkilendirilebilir bir konteynere ihtiyacınız vardır
  • Öğelerin sıralanması gerekiyorsa, siparişli bir ilişkilendirici konteynere ihtiyacınız vardır
  • Aksi takdirde 2. soruya atlayın.

Soru 1.1: Sipariş edildi mi?

  • Belirli bir siparişe ihtiyacınız yoksa, bir unordered_kap kullanın, aksi takdirde geleneksel sipariş edilen karşılığı kullanın.

Soru 1.2: Ayrı Anahtar ?

  • Anahtar değerden ayrıysa, a kullanın map, aksi takdirde aset

Soru 1.3: Kopyalar ?

  • Yinelenenleri tutmak istiyorsanız, a kullanın multi, aksi halde kullanmayın .

Misal:

Kendileriyle ilişkilendirilmiş benzersiz bir kimliği olan birkaç kişim olduğunu ve bir kişinin verilerini kimliğinden olabildiğince basit bir şekilde almak istediğimizi varsayalım.

  1. Bir findişlev, dolayısıyla ilişkisel bir kap istiyorum

    1.1. Siparişi daha az umursamadım, bu yüzden bir unordered_konteyner

    1.2. Anahtarım (ID) ilişkilendirildiği değerden ayrı, bu nedenle birmap

    1.3. Kimlik benzersizdir, bu nedenle hiçbir kopya içeri girmemelidir.

Nihai cevaptır: std::unordered_map<ID, PersonData>.


Soru 2: Bellek kararlı mı?

  • Elemanlar bellekte kararlıysa (yani, kabın kendisi değiştirildiğinde değişiklik yapmamalıdır), o zaman bazı list
  • Aksi takdirde 3. soruya atlayın.

Soru 2.1: Hangisi ?

  • Bir yerleşme list; a forward_listyalnızca daha az bellek alanı için kullanışlıdır.

Soru 3: Dinamik boyutta mı?

  • Kapsayıcı bilinen bir boyuta (derleme zamanında) sahipse ve bu boyut program sırasında değiştirilmeyecekse ve öğeler varsayılan olarak oluşturulabilirse veya tam bir başlatma listesi ( { ... }sözdizimi kullanarak ) sağlayabilirsiniz , ardından bir array. Geleneksel C-dizisinin yerini alır, ancak kullanışlı işlevlerle.
  • Aksi takdirde 4. soruya geçin.

Soru 4: Çift uçlu mu?

  • Önden ve arkadan öğeleri kaldırabilmek istiyorsanız, a kullanın deque, aksi takdirde a kullanın vector.

Varsayılan olarak, ilişkilendirilebilir bir kapsayıcıya ihtiyacınız olmadığı sürece seçiminizin bir olacağını unutmayın vector. Aynı zamanda Sutter ve Stroustrup'un tavsiyesi olduğu ortaya çıkıyor .


5
+1, ancak bazı notlarla: 1) arrayvarsayılan yapılandırılabilir bir tür gerektirmez; 2) multis'yi seçmek, kopyalara izin verilmesiyle ilgili değildir, ancak onları saklamanın önemli olup olmadığı hakkında daha fazladır (kopyaları multikapsayıcılara koyabilirsiniz , sadece bir tanesinin tutulduğu görülür).
R. Martinho Fernandes

2
Örnek biraz kapalı. 1) ilişkisel olmayan bir kapta "üye" (üye işlevi değil, "<algoritma" ") bulabiliriz, 1.1)" verimli "bulmamız gerekiyorsa ve sıralanmamış_ O (1) olur ve O (1) olmaz log n).
BlakBat

4
@BlakBat: olsa map.find(key)da çok daha lezzetli std::find(map.begin(), map.end(), [&](decltype(map.front()) p) { return p.first < key; }));, bu yüzden anlamsal olarak, findbir üye işlevinden ziyade bir üye işlevidir <algorithm>. O (1) ve O (log n) 'e gelince, anlambilimi etkilemez; Örnekten "verimli bir şekilde" çıkaracağım ve "kolayca" ile değiştireceğim.
Matthieu M.

"Elemanlar bellekte kararlı olmalıysa ... o zaman bir liste kullan" ... hmmm, ben de dequebu özelliğe sahip olduğunu düşündüm ?
Martin Ba

@MartinBa: Evet ve hayır. A dequeöğelerinde yalnızca her iki uçtan / it tuşuna basarsanız kararlıdır ; ortaya yerleştirmeye / silmeye başlarsanız, oluşturulan boşluğu doldurmak için en fazla N / 2 öğesi karıştırılır.
Matthieu M.5

51

Matthieu'nun cevabını seviyorum, ancak akış şemasını şu şekilde yeniden ifade edeceğim:

Std :: vector ne zaman KULLANILMAZ

Varsayılan olarak, bir kapsayıcıya ihtiyacınız varsa kullanın std::vector. Böylece, diğer her kap sadece bazı işlev alternatifleri sağlayarak haklı çıkar std::vector.

Kurucular

std::vectoriçeriğinin hareket ettirilebilir olmasını gerektirir, çünkü etrafındaki öğeleri karıştırması gerekir. Bu, içeriklerin üzerine yerleştirmek için korkunç bir yük değildir ( vb. Sayesinde varsayılan kuruculara gerek olmadığını unutmayın emplace). Bununla birlikte, diğer konteynerlerin çoğu herhangi bir özel kurucuya ihtiyaç duymaz (yine sayesinde emplace). Eğer kesinlikle bir nesne var ise olamaz bir hamle yapıcı uygulamak, o zaman başka bir şey almak zorunda kalacaktır.

A std::deque, birçok özelliğe sahip olan genel değiştirme olacaktır std::vector, ancak sadece deque'nin her iki ucuna da ekleyebilirsiniz. Ortadaki uçlar hareket etmeyi gerektirir. A std::list, içeriğine gerek duymaz.

Bools İhtiyaçları

std::vector<bool>değil. Standart. Ancak normalde izin verilen vectoroperasyonlar std::vectoryasaklandığından , olağan anlamda bir değildir . Ve kesinlikle s içermezbool .

Bu nedenle, vectorbir bools kabından gerçek davranışa ihtiyacınız varsa , bunu elde edemezsiniz std::vector<bool>. Bu yüzden a std::deque<bool>.

Aranıyor

Bir kapsayıcıdaki öğeleri bulmanız gerekiyorsa ve arama etiketi yalnızca bir dizin std::vectorolamazsa, setve işaretinden vazgeçmeniz gerekebilir map. " May " anahtar kelimesine dikkat edin ; bir sıralama std::vectorbazen makul bir alternatiftir. Veya Boost.Container's flat_set/map, sıralanan uygular std::vector.

Şimdi bunların her birinin kendi ihtiyaçları olan dört varyasyonu vardır.

  • mapArama etiketi, aradığınız öğeyle aynı şey olmadığında bir kullanın . Aksi takdirde a set.
  • Kapsayıcıda çok sayıda öğeniz unorderedolduğunda kullanın ve arama performansının kesinlikle yerine olması gerekir .O(1)O(logn)
  • multiAynı arama etiketine sahip olmak için birden fazla öğeye ihtiyacınız varsa kullanın .

Sipariş

Her zaman belirli bir karşılaştırma işlemine göre sıralanacak bir öğe kabına ihtiyacınız varsa, a set. Veya multi_setaynı değere sahip birden fazla öğeye ihtiyacınız varsa.

Veya bir sıralı kullanabilirsiniz std::vector, ancak sıralı tutmanız gerekir.

istikrar

Yineleyiciler ve referanslar geçersiz kılındığında bazen endişe kaynağı olur. Başka bir yerdeki bu öğelere yineleyiciler / işaretçiler olacak şekilde bir öğe listesine ihtiyacınız varsa, std::vectorgeçersiz kılma yaklaşımına uygun olmayabilir. Herhangi bir takma işlemi, geçerli boyuta ve kapasiteye bağlı olarak geçersizliğe neden olabilir.

std::listkesin bir garanti sunar: bir yineleyici ve ilişkili referansları / işaretçileri yalnızca öğenin kaptan çıkarılması durumunda geçersiz olur. std::forward_listeğer bellek ciddi bir endişe ise.

Bu çok güçlü bir garanti ise std::deque, daha zayıf ama kullanışlı bir garanti sunar. Geçersiz kılma, ortadaki yerleştirmelerden kaynaklanır, ancak baş veya kuyruktaki eklemeler , kaptaki öğelere işaretçiler / referanslar değil, yalnızca yineleyicilerin geçersiz kılınmasına neden olur .

Ekleme Performansı

std::vector sadece sonunda ucuz yerleştirme sağlar (ve hatta o zaman bile, eğer üfleme kapasitesiniz pahalı olur).

std::listperformans açısından pahalıdır (yeni eklenen her öğe bir bellek ayırmaya mal olur), ancak tutarlıdır . Ayrıca, neredeyse hiç performans maliyeti olmadan öğeleri karıştırmak std::listve aynı zamanda performans kaybı olmadan aynı tipteki diğer kaplarla ticaret yapmak için vazgeçilmez bir yetenek sunar . Bir şeyleri çok karıştırmanız gerekiyorsa , kullanın std::list.

std::dequebaş ve kuyrukta sabit zamanlı yerleştirme / çıkarma sağlar, ancak ortadaki yerleştirme oldukça pahalı olabilir. Öyleyse önden ve arkadan bir şeyler eklemeniz / çıkarmanız gerekiyorsa, ihtiyacınız olan şey std::dequeolabilir.

Hareket semantiği sayesinde std::vectorekleme performansının eskisi kadar kötü olmayabileceğine dikkat edilmelidir. Bazı uygulamalar bir anlam semantik tabanlı öğe kopyalama ("swaptimization" olarak adlandırılır) biçimini uyguladı, ancak şimdi hareket dilin bir parçası olduğu için standart tarafından zorunlu kılındı.

Dinamik Ayırma Yok

std::arraymümkün olan en az dinamik ayırmayı istiyorsanız iyi bir kapsayıcıdır. Bu sadece bir C-dizisinin etrafını saran bir paket; bu, derleme zamanında boyutunun bilinmesi gerektiği anlamına gelir . Bununla yaşayabiliyorsanız, kullanın std::array.

Bununla birlikte , bir boyut kullanmak std::vectorve kullanmak, reservesınırlı bir şekilde de işe yarayacaktır std::vector. Bu şekilde, gerçek boyut değişebilir ve yalnızca bir bellek ayırma elde edersiniz (kapasiteyi patlatmadıkça).


1
Eh, çok :) WRT bir vektör tutarak çok sıralanmış Cevabını gibi dışında std::sortda var std::inplace_mergekolayca (yerine daha yeni unsurlar yerleştirmek için ilginç olan std::lower_bound+ std::vector::insertçağrısı). Öğrenmek güzel flat_setve flat_map!
Matthieu M.

2
Ayrıca 16 baytlık hizalanmış tiplerde bir vektör kullanamazsınız. Ayrıca iyi bir yedek vector<bool>olduğunu vector<char>.
Ters

@Inverse: "16 baytlık hizalanmış tiplerde bir vektör de kullanamazsınız." Kim söylüyor? Bu std::allocator<T>hizalamayı desteklemiyorsa (ve neden böyle bir şey yapmayacağını bilmiyorum), her zaman kendi özel ayırıcısını kullanabilirsiniz.
Nicol Bolas

2
@ Ters: C ++ 11'lerin std::vector::resizebir değer almayan aşırı yüklenmesi vardır (sadece yeni boyutu alır; yeni öğeler varsayılan olarak yerinde oluşturulacaktır). Ayrıca, derleyiciler neden bu hizalamaya sahip oldukları bildirilse bile değer parametrelerini düzgün bir şekilde hizalayamıyor?
Nicol Bolas

1
bitsetönceden boyutu biliyorsanız bool için en.cppreference.com/w/cpp/utility/bitset
bendervader


1

İşte hızlı bir dönüş, muhtemelen işe ihtiyacı var

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

Bunun , esas olarak bağlantılı düğümleri sevmeme bağlı olarak, C ++ 03 sürümünden çılgınca farklı olduğunu fark edebilirsiniz . Bağlantılı düğüm kapları, nadir görülen durumlar dışında genellikle bağlantısız bir kap tarafından performansta atılabilir. Bu durumların ne olduğunu bilmiyorsanız ve artırmaya erişiminiz varsa, bağlantılı düğüm kaplarını kullanmayın. (std :: list, std :: slist, std :: map, std :: multimap, std :: set, std :: multiset). Bu liste çoğunlukla küçük ve orta taraflı kaplara odaklanır, çünkü (A) kodda uğraştığımızın% 99,99'udur ve (B) Çok sayıda öğe farklı kapsayıcılara değil, özel algoritmalara ihtiyaç duyar.

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.