web-dev-qa-db-ja.com

C ++のstd :: vectorとstd :: array

C++のstd::vectorstd::arrayの違いは何ですか?あるものが別の場合よりも優先されるべきなのはいつですか?それぞれの長所と短所は何ですか?私の教科書は、それらがどのように同じであるかをリストしています。

255
Zud

std::vectorは、動的配列をカプセル化するテンプレートクラスです1、ヒープに格納され、要素が追加または削除されると自動的に拡大および縮小します。すべてのフック(begin()end()、反復子など)を提供して、STLの残りの部分で正常に動作するようにします。また、通常の配列では面倒な操作を実行できる便利なメソッドもいくつかあります。ベクターの中央に要素を挿入します(背後にある次の要素を移動するすべての作業を処理します)。

ヒープに割り当てられたメモリに要素を格納するため、静的配列に関していくらかのオーバーヘッドがあります。

std::arrayは、静的なサイズの配列をカプセル化するテンプレートクラスであり、オブジェクト自体の内部に格納されます。つまり、クラスをスタックにインスタンス化すると、配列自体がスタックに配置されます。そのサイズはコンパイル時に認識される必要があり(テンプレートパラメーターとして渡されます)、拡大または縮小することはできません。

std::vectorよりも制限がありますが、実際にはほとんどがCスタイルの配列の軽量ラッパーであるため、特に小さなサイズの場合はより効率的です。ただし、ポインターへの暗黙的な変換が無効になっているため、より安全です。また、std::vectorおよびその他のコンテナーのSTL関連機能の多くを提供するため、STLアルゴリズムおよびcoで簡単に使用できます。とにかく、固定サイズの非常に制限のために、それはstd::vectorよりもはるかに柔軟ではありません。

std::arrayの概要については、 この記事 をご覧ください。 std::vectorとその上で可能な操作の簡単な紹介については、 documentation をご覧ください。


  1. 実際、標準では異なる操作の最大の複雑さ(例えば、一定時間でのランダムアクセス、線形時間でのすべての要素の繰り返し、一定の償却時間での要素の追加と削除、など)、しかし、私の知る限り、動的な配列を使用する以外に、そのような要件を満たす他の方法はありません。 @Lucretielで述べられているように、標準では実際には要素が連続して格納されることが必要であるため、itは動的配列であり、関連するアロケーターが置く場所に格納されます。
286
Matteo Italia

std::vector<T>クラスの使用:

  • ... is ちょうど速い組み込み配列を使用する場合と同じように、組み込み配列でできること(既存の要素の読み取りと書き込み)のみを行うと仮定します。

  • ...新しい要素が挿入されると自動的にサイズ変更されます。

  • ...新しい要素を挿入することができます開始時または中間ベクトルの残りの要素を自動的に「シフト」しますセンス?)。 std::vectorの任意の場所にある要素を削除することもでき、残りの要素は自動的に下に移動します。

  • ... at()メソッドを使用して範囲チェック読み取りを実行できます(このチェックを実行したくない場合は、常にインデクサー[]を使用できます)。

がある  std::vector<T>の使用に関する3つの主な注意事項:

  1. 基礎となるポインターへの信頼できるアクセスがありません。これは、配列のアドレスを要求するサードパーティの関数を扱う場合、mayが問題になる可能性があります。

  2. std::vector<bool>クラスはばかげています。配列としてではなく、圧縮ビットフィールドとして実装されています。 boolsの配列が必要な場合は避けてください!

  3. 使用中、std::vector<T>sは、同じ要素数のC++配列よりも少し大きくなります。これは、現在のサイズなど、他の少量の情報を追跡する必要があり、std::vector<T>sのサイズが変更されるたびに、必要以上のスペースを予約するためです。これは、新しい要素が挿入されるたびにサイズを変更する必要がないようにするためです。この動作は、カスタムallocatorを提供することで変更できますが、それを行う必要性を感じたことはありませんでした!


編集:質問に対するZudの返信を読んだ後、私はこれを追加する必要があると感じました:

std::array<T>クラスは、C++配列とは異なります。 std::array<T>は、C++配列の非常に薄いラッパーであり、クラスのユーザーからポインターを隠すことを主な目的としています(C++では、配列は暗黙的にポインターとしてキャストされ、多くの場合、効果を失います)。 std::array<T>クラスはそのサイズ(長さ)も保存します。これは非常に便利です。

15
ClosureCowboy

@MatteoItaliaによるポイントを強調するために、効率の違いはデータの保存場所です。ヒープメモリ(vectorで必要)は、メモリを割り当てるためにシステムを呼び出す必要があります。これは、サイクルをカウントしている場合は高価になる可能性があります。スタックメモリ(arrayの可能性)は、時間に関しては実質的に「ゼロオーバーヘッド」です。これは、メモリはスタックポインタを調整するだけで割り当てられ、関数に入るときに1回だけ実行されるためです。スタックは、メモリの断片化も回避します。確かに、std::arrayは常にスタック上にあるとは限りません。割り当てる場所によって異なりますが、ベクターに比べてヒープからのメモリ割り当てが1つ少なくなります。あなたが持っている場合

  • 小さな「配列」(100要素未満と言う)-(典型的なスタックは約8MBなので、スタックに数KB以上を割り当てたり、コードが再帰的な場合はそれ以下に割り当てないでください)
  • サイズは固定されます
  • ライフタイムは関数スコープ内にあります(または親クラスと同じライフタイムを持つメンバー値です)
  • あなたはサイクルを数えている、

必ずベクター上でstd::arrayを使用してください。これらの要件のいずれかが当てはまらない場合は、std::vectorを使用します。

14
Mark Lakata

多次元配列の使用を検討している場合、std :: arrayとstd :: vectorにはさらに1つの違いがあります。多次元のstd :: arrayは、cスタイルの配列と同様に、すべての次元でメモリに要素がパックされます。多次元std :: vectorは、すべての次元でパックされるわけではありません。

次の宣言がある場合:

int cConc[3][5];
std::array<std::array<int, 5>, 3> aConc;
int **ptrConc;      // initialized to [3][5] via new and destructed via delete
std::vector<std::vector<int>> vConc;    // initialized to [3][5]

Cスタイル配列(cConc)またはstd :: array(aConc)の最初の要素へのポインターは、先行する各要素に1を追加することにより、配列全体で反復できます。ぎっしり詰まっています。

ベクトル配列(vConc)またはポインター配列(ptrConc)の最初の要素へのポインターは、最初の5つの要素(この場合)でのみ反復でき、その後、12バイト(私のシステム上)のオーバーヘッドがあります。次のベクトル。

これは、[3] [1000]配列として初期化されたstd :: vector>配列は、[1000] [3]配列として初期化されたものよりもメモリ内ではるかに小さく、両方ともメモリ内でstd:よりも大きいことを意味します:いずれかの方法で割り当てられた配列。

これはまた、メモリのオーバーヘッドを考慮せずに単純に多次元ベクトル(またはポインタ)配列をopenGLに渡すことができないことを意味しますが、単純に多次元std :: arrayをopenGLに渡して解決させることができます。

9
psimpson