web-dev-qa-db-ja.com

実世界でのC ++ std :: vector vs array

私はC++を初めて使用します。 MichaelDawsonによる「BeginningC++ ThroughGameProgramming」を読んでいます。しかし、私は一般的にプログラミングに不慣れではありません。ベクトルを扱った章を終えたばかりなので、実世界でのベクトルの使用について質問があります(私はコンピューターサイエンスの学生なので、実世界での経験はまだあまりありません)。

著者は各章の終わりにQ/Aを持っており、そのうちの1つは次のとおりです。

Q:配列の代わりにベクトルを使用する必要があるのはいつですか?

A:ほとんどの場合。ベクトルは効率的で柔軟性があります。アレイよりも少し多くのメモリが必要ですが、このトレードオフはほとんどの場合、メリットに見合う価値があります。

皆さんはどう思いますか? Javaの本でベクトルについて学んだことを覚えていますが、Comp。Sci。入門クラスでも、大学のデータ構造クラスでも、ベクトルについてはまったく取り上げていませんでした。プログラミングの割り当て(JavaとC)で使用されるのを見たことがありません。これにより、学校のコードと実際のコードは非常に異なる可能性があることはわかっていますが、あまり使用されていないように感じます。

2つのデータ構造の違いについて説明する必要はありません。私はそれらをよく知っています。私が知りたいのは、作者がQ/Aで良いアドバイスをしているのか、それとも、固定サイズのデータ​​構造を管理する複雑さで初心者プログラマーが自分自身を破壊するのを防ごうとしているのかということだけです。また、作者のアドバイスについてどう思うかに関わらず、現実の世界でもっと頻繁に何をしますか参照

23
GRardB

A:ほとんどの場合[配列の代わりにベクトルを使用してください]。ベクトルは効率的で柔軟性があります。アレイよりも少し多くのメモリが必要ですが、このトレードオフはほとんどの場合、メリットに見合う価値があります。

それは過度に単純化されています。配列を使用することはかなり一般的であり、次の場合に魅力的です。

  • 要素はコンパイル時に指定されます。 const char project[] = "Super Server";const Colours colours[] = { Green, Yellow };

    • c ++ 11では、std::vectorsを値で初期化することも同様に簡潔になります
  • 要素の数は本質的に固定されています。 const char* const bool_to_str[] = { "false", "true" };Piece chess_board[8][8];

  • 初回使用時のパフォーマンスは重要です。定数の配列を使用すると、コンパイラは完全に事前に初期化されたオブジェクトのメモリスナップショットを実行可能イメージに書き込むことができ、実行可能イメージはすぐに使用できるように直接ページフォールトされるため、通常ははるかに高速です。ランタイムヒープ割り当て(new[])とそれに続くオブジェクトのシリアル化された構築

    • コンパイラによって生成されたconstデータのテーブルは、常に複数のスレッドで安全に読み取ることができますが、実行時に構築されたデータは、非関数ローカルstatic変数のコンストラクタによってトリガーされた他のコードがそのデータを使用しようとする前に、構築を完了する必要があります。シングルトンのいくつかの方法(おそらくスレッドセーフで、さらに遅くなります)

    • C++ 03では、初期サイズで作成されたvectorsは、1つのプロトタイプ要素オブジェクトを作成してから、各データメンバーをコピー作成します。つまり、構築が意図的に操作なしとして残されたタイプの場合でも、データ要素をコピーするコストが発生しました。つまり、メモリに残されたゴミの値を複製します。明らかに、初期化されていない要素の配列の方が高速です。

  • C++の強力な機能の1つは、特定のプロトコルに必要なメモリレイアウトを正確にモデル化するclass(またはstruct)を記述し、操作する必要のあるメモリにクラスポインタを向けて、簡単に解釈または割り当てることができることです。値。良くも悪くも、そのようなプロトコルの多くは、小さな固定サイズの配列を埋め込むことがよくあります。

  • 構造体/クラスの最後に1つの要素の配列(コンパイラが拡張として許可している場合は0)を配置し、より大きなデータ領域で構造体タイプへのポインタを狙い、アクセスするための数十年前のハックがありますメモリの可用性と内容に関する事前の知識に基づいて、構造体の最後にある配列要素(書き込み前に読み取る場合) 要素がゼロの配列の必要性は何ですか? を参照してください。

  • 配列を含むクラス/構造体は引き続きPODタイプにすることができます

  • 配列は、複数のプロセスからの共有メモリへのアクセスを容易にします(デフォルトでは、動的に割り当てられた実際のデータへのvectorの内部ポインタは共有メモリにないか、プロセス間で意味がありません。C++ 03vectorsに次のような共有メモリを使用させることは有名です。これは、カスタムアロケータテンプレートパラメータを指定する場合でも同様です)。

  • アレイを埋め込むと、メモリアクセス要件をローカライズできるため、キャッシュヒットが向上し、パフォーマンスが向上します。

とはいえ、(コードの簡潔さ、読みやすさ、パフォーマンスの点で)vectorを使用することが積極的な苦痛ではない場合は、そうするほうがよいでしょう。彼らはsize()を実行し、at()を介してランダムアクセスをチェックしました。イテレータ、サイズ変更(アプリケーションの「成熟」として必要になることが多い)など。また、必要に応じてvectorから他の標準コンテナに変更する方が簡単で、標準アルゴリズムを適用する方が安全で簡単です(x.end()x + sizeof x / sizeof x[0]よりも優れています)。

更新:C++ 11はstd::array<>を導入しました。これは、vectorsのコストの一部を回避します-内部的に固定サイズの配列を使用して、余分なヒープの割り当て/割り当て解除を回避します-いくつかの利点とAPI​​機能を提供します:- http://en.cppreference.com/w/cpp/container/array

26
Tony Delroy

配列ではなくvectorを使用する最も良い理由の1つは、[〜#〜] raii [〜#〜]イディオムです。基本的に、c ++コードを例外安全にするためには、動的に割り当てられたメモリやその他のリソースをオブジェクト内にカプセル化する必要があります。これらのオブジェクトには、これらのリソースを解放するデストラクタが必要です。

例外が処理されない場合、呼び出されることが保証されているのは、スタック上のオブジェクトのデストラクタだけです。オブジェクトの外部にメモリを動的に割り当て、それが削除される前にキャッチされなかった例外がどこかにスローされた場合、メモリリークが発生します。

これは、deleteの使用を忘れないようにするための優れた方法でもあります。

std::algorithmもチェックする必要があります。これは、vectorおよびその他のSTLコンテナに多くの一般的なアルゴリズムを提供します。

vectorを使用してコードを記述したことがいくつかありますが、振り返ってみると、おそらくネイティブ配列の方が優れていたでしょう。しかし、これらすべての場合において、Boost::multi_arrayまたはBlitz::Arrayのいずれかがどちらよりも優れていたでしょう。

19
Dan

科学と工学で使用される大きなサイズの配列/ベクトルをコーディングするために、ここで私の意見をポップします。

この場合のポインタベースの配列は、特に標準タイプの場合、かなり高速になる可能性があります。ただし、ポインタはメモリリークの危険性を追加します。これらのメモリリークにより、デバッグサイクルが長くなる可能性があります。さらに、ポインタベースの配列を動的にしたい場合は、これを手動でコーディングする必要があります。

一方、標準タイプの場合、ベクトルは遅くなります。また、動的に割り当てられたポインターをstlベクトルに格納しない限り、動的でメモリセーフでもあります。

科学と工学では、選択はプロジェクトによって異なります。速度とデバッグ時間の違いはどれくらいですか?たとえば、シミュレーションソフトウェアであるLAAMPSは、メモリ管理クラスを介して処理される生のポインタを使用します。このソフトウェアでは速度が優先されます。私が構築しているソフトウェアは、速度とメモリフットプリントおよびデバッグ時間のバランスをとる必要があります。デバッグに多くの時間を費やしたくないので、STLベクターを使用しています。

大規模なアレイの広範なテストとWebの多くの読み取りから発見した、この回答にさらに情報を追加したいと思いました。したがって、stlベクトルと大きなサイズの配列(100万以上)に関する別の問題は、これらの配列にメモリがどのように割り当てられるかで発生します。 Stlベクトルは、メモリを処理するためにstd :: allocatorクラスを使用します。このクラスは、プールベースのメモリアロケータです。小規模なロードでは、プールベースの割り当ては速度とメモリ使用量の点で非常に効率的です。ベクトルのサイズが数百万に達すると、プールベースの戦略はメモリを大量に消費します。これは、プールの傾向が常にstlベクトルによって現在使用されているよりも多くのスペースを保持する傾向があるために発生します。

大規模なベクターの場合は、独自のベクタークラスを作成するか、ポインター(rawまたはboostまたはc ++ライブラリのある種のメモリ管理システム)を使用することをお勧めします。両方のアプローチには長所と短所があります。選択は、実際に取り組んでいる正確な問題によって異なります(ここに追加するには変数が多すぎます)。独自のベクトルクラスを作成する場合は、そのベクトルがメモリをクリアする簡単な方法であることを確認してください。現在、Stlベクトルの場合、最初にクラスに実際に組み込まれているはずのことを行うために、スワップ操作を使用する必要があります。

5
Zachary Kraus

Std :: vectorは、サイズ変更可能な配列です。それだけではありません。これはインテリジェントなデータ構造ではないため、データ構造クラスで学習することではありません。

現実の世界では、たくさんの配列があります。しかし、「CwithClasses」スタイルのC++プログラミングを使用するレガシーコードベースもたくさんあります。それはあなたがすべきそのようにプログラムするという意味ではありません。

4
Nicol Bolas

既知のサイズの固定コレクションを扱うのは、現実の世界ではまれなケースです。ほとんどすべての場合、プログラムに収容するデータセットの正確なサイズにはある程度の不明な点があります。確かに、goodプログラムの特徴は、考えられるさまざまなシナリオに対応できることです。

これらの(些細な)シナリオを例として取り上げます。

  • FPSでAI戦闘員を追跡するためのビューコントローラーを実装しました。ゲームロジックは、数秒ごとにさまざまなゾーンにランダムな数の戦闘員を生み出します。プレイヤーは、実行時にのみ知られている速度でAI戦闘員をダウンさせています。
  • 弁護士が州の地方裁判所のウェブサイトにアクセスし、夜間に発生した新しい飲酒運転の件数を照会しています。彼は、事故が発生した時間、郵便番号、逮捕した警官などの一連の変数でリストをフィルタリングすることを選択しました。
  • オペレーティングシステムは、オペレーティングシステムで実行されているさまざまなプログラムによって使用されているメモリアドレスのリストを維持する必要があります。プログラムの数とそのメモリ使用量は、予測できない方法で変化します。

これらのいずれの場合でも、可変サイズのリスト(動的な挿入と削除に対応)は単純な配列よりもパフォーマンスが優れているという良い議論をすることができます。主な利点は、固定配列に要素を追加または削除するときに、固定配列のメモリスペースを割り当て/割り当て解除する必要性が減少することです。

3
Perception

経験則:要素の数が事前にわからない場合、または要素の数が多い(たとえば、10を超える)と予想される場合は、ベクトルを使用します。それ以外の場合は、配列を使用することもできます。たとえば、私は多くのジオメトリ処理コードを記述し、線を2座標のARRAYとして定義します。線は2点で定義され、常に正確に2点で定義されます。配列の代わりにベクトルを使用すると、多くの点で、パフォーマンスの面でもやり過ぎになります。

別のこと:私が「配列」と言うとき、私は本当に配列を意味します:int evenOddCount[2];などの配列構文を使用して宣言された変数ベクトルとint *evenOddCount = new int[2];などの動的に割り当てられたメモリブロックのどちらかを選択することを検討する場合、答えは明らかです:VECTORを使用してください!

3
zvrba

配列に関する限り、単純な整数または文字列配列は非常に使いやすいです。一方、検索、並べ替え、挿入、削除などの一般的な関数の場合、ベクトルで標準アルゴリズム(ライブラリ関数に組み込まれている)を使用すると、はるかに高速になります。特に、オブジェクトのベクトルを使用している場合。次に、より多くのオブジェクトが挿入されると、ベクトルのサイズが動的に大きくなる可能性があるという大きな違いがあります。お役に立てば幸いです。

0
Geek