C ++ 11で標準ライブラリコンテナーを効率的に選択するにはどうすればよいですか?


135

「C ++コンテナーの選択」と呼ばれるよく知られたイメージ(チートシート)があります。必要な用途に最適なコンテナを選択するためのフローチャートです。

すでにC ++ 11バージョンがあるかどうか誰かが知っていますか?

これは前のものです: eC ++コンテナーの選択


6
これまでに見たことがない。ありがとう!
WeaselFox

6
@WeaselFox:これはすでにSOのC ++- Faqの一部です。
Alok Save

4
C ++ 11で導入された新しい真のコンテナータイプは、unordered_Xコンテナーのみです。ハッシュテーブルが適切かどうかを判断する際には多くの考慮事項があるため、それらを含めるとテーブルがかなり混乱するだけです。
Nicol Bolas、2012年

13
Jamesは正解です。表に示されているものよりもベクターを使用するケースが多くあります。データ局所性の利点は、多くの場合、一部の操作の効率の欠如より優れています(すぐにC ++ 11)。私は、電子チャートを見つけることができませんCのために有用であっても++ 03そう
デビッド・ロドリゲス- dribeas

33
これはかわいいですが、データ構造に関する一般的な教科書を読むと、このフローチャートを数分で再発明できるだけでなく、このフローチャートで説明しているより多くの便利なものも理解できる状態になると思います。
アンドリュートマゾス

回答:


97

私が知っていることではありませんが、テキストで行うことができると思います。また、list一般的にはそれほど良いコンテナではなく、どちらもそうではないため、チャートは少しずれていますforward_list。どちらのリストも、ニッチアプリケーションに特化したコンテナです。

このようなグラフを作成するには、次の2つの簡単なガイドラインが必要です。

  • 最初にセマンティクスを選択してください
  • いくつかの選択肢がある場合は、最も単純な

通常、最初はパフォーマンスを気にする必要はありません。大きなOの考慮事項は、数千(またはそれ以上)のアイテムの処理を開始したときにのみ実際に始まります。

コンテナには2つの大きなカテゴリがあります。

  • 連想コンテナ:findオペレーションがあります
  • 単純なシーケンスコンテナー

その後、あなたがそれらの上にいくつかのアダプタを構築することができます:stackqueuepriority_queue。アダプターはここでは省略します。認識できるように十分に特殊化されています。


質問1:連想

  • 1つずつ簡単に検索する必要がある場合キーでする必要がある場合は、連想コンテナが必要です。
  • 要素を並べ替える必要がある場合は、順序付けられた連想コンテナが必要です
  • それ以外の場合は、質問2に進んでください。

質問1.1:注文しましたか?

  • 特定の注文が必要ない場合は、unordered_コンテナーを使用します。それ以外の場合は、従来の注文済みの対応物を使用します。

質問1.2:別のキー

  • キーが値と異なる場合はを使用しmap、それ以外の場合はset

質問1.3:重複していますか?

  • 重複を保持するmulti場合は、を使用します。それ以外の場合は使用しないでください。

例:

一意のIDが関連付けられた複数の人物がいて、そのIDからできるだけ簡単に人物データを取得したいとします。

  1. が欲しい find機能を、これ連想コンテナ

    1.1。私は秩序をあまり気にすることができなかったので、unordered_コンテナを

    1.2。私のキー(ID)は、関連付けられている値とは異なるため、map

    1.3。IDは一意であるため、重複して侵入することはできません。

最終的な答えは次のとおりstd::unordered_map<ID, PersonData>です。


質問2:メモリは安定していますか?

  • 要素がメモリ内で安定している必要がある場合(つまり、コンテナ自体が変更されたときに要素が移動しないようにする必要がある場合)、 list
  • それ以外の場合は、質問3に進んでください。

質問2.1:どっち

  • のために落ち着くlist; a forward_listは、メモリ使用量が少ない場合にのみ役立ちます。

質問3:動的なサイズですか?

  • 容器は、(コンパイル時に)既知のサイズを有し、場合、このサイズは、プログラムの過程で変更されないであろう、の要素が構築可能既定されているか、あなたは(使用して完全な初期化リストを提供することができます{ ... }構文)、そして使用array。従来のCアレイを置き換えますが、便利な機能を備えています。
  • それ以外の場合は、質問4に進んでください。

質問4:ダブルエンド

  • 前面と背面の両方からアイテムを削除できるようにしたい場合は、 deque場合は、を使用しvectorます。それ以外の場合はを使用します。

連想コンテナが必要でない限り、デフォルトではが選択されることに注意してくださいvector。それはまた、SutterとStroustrupの推奨事項でもあります。


5
+1。ただし、いくつかの注意事項があります。1)arrayデフォルトの構成可能なタイプを必要としません。2)multisを選択すること許可た重複についてではなく、それらを保持することが重要であるかどうかについての詳細です(非multiコンテナに重複を置くことができますが、保持されるのは1つだけです)。
R.マルティーニョフェルナンデス

2
例は少しずれています。1)非関連コンテナで「検索」(メンバー関数ではなく、「<algorithm>」)を実行できます。1.1)「効率的に」検索する必要がある場合、unordered_はO(1)であり、O(ログn)。
BlakBat、2012年

4
@BlakBat:map.find(key)はより美味しいstd::find(map.begin(), map.end(), [&](decltype(map.front()) p) { return p.first < key; }));ので、意味的にfindは、からの関数ではなく、メンバー関数であることが重要<algorithm>です。O(1)対O(log n)については、セマンティクスには影響しません。例から「効率的に」を削除し、「簡単に」置き換えます。
Matthieu M.12年

「要素がメモリ内で安定している必要がある場合は、次にいくつかのリストを使用してください」 ...うーん、dequeこのプロパティも持っていると思いましたか?
Martin Ba

@MartinBa:はい、いいえ。a dequeでは、どちらかの端でプッシュ/ポップした場合にのみ要素が安定します。途中で挿入/消去を開始すると、作成されたギャップを埋めるために最大N / 2個の要素がシャッフルされます。
Matthieu M.

51

私はマシューの答えが好きですが、フローチャートを次のように書き直します。

std :: vectorを使用しない場合

デフォルトでは、もののコンテナが必要な場合はを使用しますstd::vector。したがって、他のすべてのコンテナは、いくつかの代替機能を提供することによってのみ正当化されますstd::vectorます。

コンストラクタ

std::vectorアイテムをシャッフルできるようにする必要があるため、コンテンツは移動構成可能である必要があります。これは、内容(デフォルトコンストラクタがあることに注意してください上の場所へのひどい負担ではありません必要はありませんおかげで、emplaceなど)。ただし、他のほとんどのコンテナは、特定のコンストラクタを必要としません(これものおかげですemplace)。したがって、移動コンストラクタを絶対に実装できないオブジェクトがある場合は、別のものを選択する必要があります。

A std::dequeは、のプロパティの多くを備えた一般的な置き換えstd::vectorですが、両端キューの両端にのみ挿入できます。真ん中のインサートは移動が必要です。A std::listはその内容を要求しません。

ブール値が必要

std::vector<bool>ではありません。まあ、それは標準です。しかしvectorstd::vector通常許可されている操作は禁止されているため、通常の意味ではありません。そしてそれは間違いなくboolsを含まない

したがって、sのvectorコンテナから実際の動作が必要な場合bool、それをから取得することはありませんstd::vector<bool>。したがって、で期限を設定する必要がありますstd::deque<bool>

検索中

あなたは、コンテナ内の要素を見つける必要がある、と検索タグだけでインデックスすることができない場合は、放棄する必要があるかもしれないstd::vectorの賛成でsetmap。キーワード「かもしれない」に注意してください。ソートstd::vectorは時々合理的な代替手段です。またはflat_set/map並べ替えを実装するBoost.Containerのstd::vector

現在、これらには4つのバリエーションがあり、それぞれ独自のニーズがあります。

  • map検索タグが、探しているアイテムと同じでない場合は、aを使用します。それ以外の場合はを使用しsetます。
  • コンテナに多くのアイテムがあり、検索パフォーマンスがでなくてはunorderedならない場合に使用します。O(1)O(logn)
  • multi複数のアイテムに同じ検索タグを付ける必要がある場合に使用します。

ご注文

特定の比較演算に基づいて常にアイテムのコンテナーをソートする必要がある場合は、を使用できますset。またはmulti_set、同じ値を持つ複数のアイテムが必要な場合。

または、ソートを使用できます std::vectorしたままにする必要があります。

安定

イテレータと参照が無効化されると、問題になることがあります。他のさまざまな場所にあるアイテムへのイテレータ/ポインタがあるように、アイテムのリストが必要な場合は、std::vector、無効化へののアプローチは適切でない場合があります。現在のサイズと容量によっては、挿入操作によって無効化が発生する場合があります。

std::list確かな保証を提供します。イテレータとそれに関連付けられた参照/ポインタは、アイテム自体がコンテナから削除されたときにのみ無効になります。std::forward_list記憶が深刻な問題である場合に存在します。

それが強すぎる保証である場合std::deque、より弱いが有用な保証を提供します。無効化は真ん中の挿入によって発生しますが、先頭または末尾に挿入すると、コンテナー内のアイテムへのポインター/参照ではなく、反復子のみが無効化されます。

挿入性能

std::vector 最後に安価な挿入のみを提供します(それでも、容量をブローすると高価になります)。

std::listパフォーマンスの点で高価です(新しく挿入された各アイテムにはメモリ割り当てが必要です)が、一貫しています。また、パフォーマンスコストをほとんどかけずにアイテムをシャッフルしたりstd::list、パフォーマンスを低下させずに同じタイプの他のコンテナーとアイテムを交換したりするために必要な場合もあります。たくさんのことをシャッフルする必要がある場合は、を使用してくださいstd::list

std::deque頭と尾に一定時間の挿入/取り外しを提供しますが、中央への挿入はかなり高価になる可能性があります。したがって、前面だけでなく背面からも物を追加/削除する必要がある場合は、必要な場合がありstd::dequeます。

移動のセマンティクスのおかげで、std::vector挿入のパフォーマンスは以前ほど悪くないことに注意してください。一部の実装では、セマンティックベースのアイテムのコピー(いわゆる「スワップティマイゼーション」)の移動の形式を実装していましたが、移動は言語の一部であるため、標準で義務付けられています。

動的割り当てなし

std::array可能な限り少ない動的割り当てが必要な場合は、良いコンテナです。これはC配列の単なるラッパーです。つまり、そのサイズはコンパイル時にわかっている必要があります。それで生活できるなら、を使ってくださいstd::array

そうは言っても、サイズの使用std::vectorreserveingは、制限付きの場合にも同様に機能しますstd::vector。このように、実際のサイズは変わる可能性があり、1つのメモリ割り当てしか得られません(容量を大きくしない限り)。


1
まあ、私もあなたの答えがとても好きです:) WRTは、ベクトルとは別に、ソートされたベクトルを維持します。(+ 呼び出しではなく)新しい要素を簡単に配置できるのstd::sortstd::inplace_merge興味深いものです。学ぶためにニースと!std::lower_boundstd::vector::insertflat_setflat_map
Matthieu M.

2
また、16バイト境界整列型のベクトルを使用することもできません。また、の良い代替品もvector<bool>ですvector<char>
インバース

@Inverse:「16バイト境界で整列された型のベクトルも使用できません。」誰が言ったのですか?std::allocator<T>がそのアラインメントをサポートしていない場合(およびそれがサポートされない理由がわからない場合)、独自のカスタムアロケーターをいつでも使用できます。
Nicol Bolas、2012年

2
@Inverse:C ++ 11にstd::vector::resizeは、値を取らないオーバーロードがあります(新しいサイズを取るだけです。新しい要素はデフォルトでインプレースで構築されます)。また、コンパイラーが値パラメーターを持つように宣言されている場合でも、コンパイラーが値パラメーターを適切に位置合わせできないのはなぜですか?
Nicol Bolas、2012年

1
bitset事前にサイズがわかっている場合はブール値en.cppreference.com/w/cpp/utility/bitset
bendervader


1

これは簡単なスピンですが、おそらく作業が必要です

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

これはC ++ 03バージョンとは大きく異なることに気づくかもしれません。これは、主にリンクノードが本当に好きではないためです。いくつかのまれな状況を除いて、リンクされたノードコンテナーは通常、リンクされていないコンテナーに比べてパフォーマンスが劣ります。これらの状況がわからない場合、およびブーストへのアクセス権がある場合は、リンクされたノードコンテナーを使用しないでください。(std :: list、std :: slist、std :: map、std :: multimap、std :: set、std :: multiset)。(A)これはコードで扱うものの99.99%であり、(B)多数の要素には異なるコンテナーではなくカスタムアルゴリズムが必要であるため、このリストは主に中小規模のコンテナーに焦点を当てています。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.