Menu

C++ の vector: std::vector による動的配列の解説

std::vector は C++ のサイズ変更可能な配列であり、デフォルトで手に取るべきコンテナです。vector の作成・アクセス・拡張・ループの方法に加え、イテレータ無効化や範囲外アクセスといった落とし穴も学びましょう。

このページのコードはエディタで実行できます - 編集してすぐに結果を確認できます。

生の配列ではなく vector を使う理由

生の配列はサイズがコンパイル時に固定で組み込まれており、関数に渡した瞬間に自分の長さを忘れてしまいます。std::vector はその両方の問題を解決します。自分の長さを追跡し、必要に応じて拡張し、自分のメモリを後始末するサイズ変更可能な配列です。モダンな C++ では vector がデフォルトのコンテナであり、生の配列に手を伸ばすのは、そうしない具体的な理由があるときだけにしましょう。

<vector> をインクルードし、要素の型を山かっこに入れて宣言します:

scores.size() は常に現在の長さを教えてくれます。同期させ続ける別の int n も不要で、sizeof のトリックも要りません。{90, 75, 100, 60}波かっこ初期化子で、vector は 4 つの枠が必要だと自分で判断します。

vector の作成と初期化

何を前もって知っているかに応じて、いくつかの作り方があります:

丸かっこ対波かっこの罠に注意してください。vector<int> tens(5, 10)10 のコピーを5 個作りますが、vector<int> tens{5, 10}510 を持つ 2 要素の vector を作ります。丸かっこは「サイズと埋める値」を意味し、波かっこは「これらのリテラル要素」を意味します。

要素の追加と削除

vector の最大の利点は拡張できることです。push_back は末尾に追加し、pop_back は末尾から削除します:

back() は最後の要素を、front() は最初の要素を返します。v[v.size() - 1]v[0] よりすっきりしています。C++11 以降では、要素をその場で構築する emplace_back(args...) も使え、重めの型では一時コピーを避けられます。

よくある初心者のミスは、空の vector に対して front()back() を呼ぶことです。これはエラーではなく未定義動作なので、必ず先に if (!v.empty()) でガードしましょう。

要素の読み取り: [] と at()

vector は配列とまったく同じように [] で添字アクセスします。ただし [] は境界チェックを行いません。範囲外のインデックスは未定義動作であり、静かにゴミを読んだり、後で分かりにくい場所でクラッシュしたりすることがあります:

vector<int> v = {1, 2, 3};
cout << v[10];   // 未定義動作 - チェックなし、エラーなし

安全性が欲しいときは at() を使いましょう。インデックスをチェックし、不正なアクセスでは std::out_of_range を投げるので、データ破壊ではなく明確な失敗が得られます:

目安: インデックスが正しいことを既に証明済みのタイトなループでは [] を、不正な入力が紛れ込みうる境界では at() を使いましょう。

vector のループ

vector をたどる最もすっきりした方法は範囲ベース for ループです。コピーせずに読むには要素を const auto& で受け取り、その場で編集するには auto& で受け取ります:

実際にインデックスが必要な場合(たとえば隣同士を比較するなど)は、昔ながらのカウントループを使います。ただし size()符号なし型(size_t)を返す点に注意してください。符号付きの int i と比較するとコンパイラ警告や意外なラップアラウンドを招くことがあるので、可能なら size_t i か範囲ベースのループを選びましょう:

for (size_t i = 0; i < v.size(); i++) {   // int ではなく size_t
    cout << v[i];
}

size、capacity、reserve

vector は 2 つの数を保持します。size()(保持している要素数)と capacity()(拡張せずに保持できる要素数)です。push_back が容量を超えると、vector はより大きなブロックを確保し、すべての要素をコピーして、古いブロックを解放します。だから繰り返しの push_back は償却的には安価でも、個々の再確保はタダではないのです:

追加する要素数がおおよそ分かっているなら、先に reserve() を呼んで繰り返しの再確保を省きましょう。reserve() は容量を変えるのであってサイズは変えない点に注意してください。要素を push するまで vector の要素数はゼロのままです。

この再確保は、vector で最もやっかいなバグの源でもあります。拡張はストレージを移動させるため、vector の中に保存したポインタ・参照・イテレータは、再確保を伴う push_back のあとでダングリングになります:

vector<int> v = {1, 2, 3};
int& first = v[0];     // vector 内への参照
v.push_back(4);        // 再確保するかもしれない...
cout << first;         // ダングリング - 解放済みメモリを指している可能性がある

同じことがイテレータにも当てはまります。保存したイテレータでループ中に push_backerase をしてはいけません。ループ中に要素を削除する必要がある場合は、erase の戻り値を使うか、std::remove を使った erase-remove イディオムを使いましょう。

次: map

vector は、位置で物を引くとき(要素 0、要素 1 など)に最適です。しかし、しばしば物をキーで引きたいことがあります。ユーザー名、商品 ID、単語などです。そのためにあるのが std::map です。次は C++ のキーと値のコンテナである map を取り上げ、エントリの挿入・検索・反復の方法、そしてほとんど誰もがつまずく「[] がデフォルト値を作ってしまう」落とし穴を解説します。

よくある質問

C++ の vector とは何ですか?

std::vector は C++ 標準ライブラリの動的(サイズ変更可能な)配列です。生の配列と違い、自分自身のサイズを把握し、push_back で要素を追加すると自動的に拡張し、メモリも自動で解放してくれます。<vector> をインクルードして vector<int> v; と書けば作成できます。

C++ の vector における [] と at() の違いは何ですか?

v[i] は境界チェックを行いません - 範囲外のインデックスは未定義動作(クラッシュや静かなデータ破壊)になります。v.at(i) はインデックスをチェックし、不正なら std::out_of_range を投げます。インデックスを既に検証済みのホットなループでは [] を、安全でデバッグしやすい失敗が欲しいときは at() を使いましょう。

push_back は C++ の vector 内へのポインタや参照を無効化しますか?

はい、その可能性があります。vector の容量が尽きると、push_back はストレージを新しいブロックへ再確保し、それによって古い要素を指すすべてのポインタ・参照・イテレータが無効になります。push_back をまたいで要素への参照を保持せず、可能なら事前に reserve() を呼んで予期しない再確保を避けましょう。

Coddy programming languages illustration

Coddyでコードを学ぼう

始める