---
title: "C++のコンテナクラスとは？種類一覧とvector・mapの使い分け【C++23・C++26対応】"
url: "https://www.issoh.co.jp/tech/details/5081/"
published: 2025-01-29
updated: 2026-10-15
categories: ["プログラミング言語"]
publisher: "株式会社一創"
---

# C++のコンテナクラスとは？種類一覧とvector・mapの使い分け【C++23・C++26対応】

C++の「コンテナ」は、複数の要素をまとめて保持し、要素の追加・削除・走査を共通の作法で行えるクラステンプレートの総称です。`std::vector`・`std::map`・`std::unordered_map` などがこれにあたり、「コンテナクラス」とも呼ばれます。この記事では、規格の分類に沿って全コンテナをヘッダー付きで一覧にし、計算量と1要素あたりのメモリ使用量、vector と map の初期化・書き方、要素型に求められる条件、イテレータ無効化、C++23・C++26で増えたコンテナまでを、コンパイル済みのコードと実測値で整理します。

## まとめ：C++のコンテナの種類と選び方の要点

- 標準のコンテナは**シーケンス・連想・非順序連想・コンテナアダプタ**の4分類。これに要素を所有しない「ビュー」（`span`・`mdspan`）が並ぶ。
- 迷ったら**vector**。要素を連続して格納し、走査や添字アクセスに向く。後述の「位置を探してから挿入する」実測（int・20,000回）では list より200倍以上速く、int を10万個入れたときのメモリは reserve なしでも list の約5分の1だった。
- キーで値を引く（辞書型・key-value）なら、順序が要らなければ **unordered\_map**、キー順の走査や範囲検索が要るなら **map**。SSOに収まらない長さの文字列キー（libc++では23文字以上）は、`std::less<>` を指定すると検索のたびの一時 string の確保を省ける。
- `vector<int> v(5)` は要素5個、`v{5}` は値5の要素1個。`reserve` があるのは vector・string・unordered 系・hive など一部で、**deque・list・map には無い**。
- C++23で **flat\_map／flat\_set**、C++26で **inplace\_vector・hive** が加わった。使えるかはコンパイラと標準ライブラリの版で決まる。

## C++のコンテナとは：要素を保持するクラステンプレートの定義

規格の第23章は、コンテナを「情報の集まりを整理するためにC++プログラムが使える部品」と位置づけています。vector・list・map などは `begin()`／`end()` でイテレータを返し、`size()`・`empty()` を持つという共通の要件（container requirements）を満たすので、範囲 for 文やイテレータを受け取るアルゴリズムで同じように走査できます。例外もあり、forward\_list は `size()` を持たず、stack などのアダプタはイテレータを持ちません。また `std::sort` はランダムアクセスイテレータを要求するため、list や map には使えません（list はメンバ関数の `sort()` を使います）。要素の型はテンプレート引数で指定するため、`std::vector<int>` も `std::vector<std::string>` も同じクラステンプレートから生成されます。型を引数に取る設計は他言語のジェネリクスにあたり、言語ごとの違いは[ジェネリクスの解説記事](/tech/details/3535/)で比較しています。

「STL」は、C++98の標準化の際に取り込まれた Standard Template Library に由来する通称です。現在の規格にSTLという章はなく、コンテナ・イテレータ・アルゴリズムはそれぞれ「C++標準ライブラリ」の一部として定義されています。そのため「STLのコンテナ」と「標準ライブラリのコンテナ」は同じものを指すと考えて差し支えありません。テンプレートそのものの仕組みは[メタプログラミングの解説記事](/tech/details/3510/)で、テンプレートによる抽象化が実行時コストを生まない理由は[ゼロコスト抽象化の記事](/tech/details/4440/)で扱っています。

## C++標準コンテナの一覧：4分類とヘッダー

C++ワーキングドラフトの [\[containers.general\]](https://eel.is/c++draft/containers.general) にある「Containers library summary」表と各節の冒頭にもとづき、全コンテナを分類ごとに並べると次のとおりです（2026年10月参照）。「追加」は初めて規格に入った版です。

| 分類    | コンテナ                               | ヘッダー              | 追加    | 内部構造                  |
| ----- | ---------------------------------- | ----------------- | ----- | --------------------- |
| シーケンス | vector                             | <vector>          | C++98 | 連続メモリの可変長配列           |
| シーケンス | deque                              | <deque>           | C++98 | 固定長ブロックの集まり           |
| シーケンス | list                               | <list>            | C++98 | 双方向連結リスト              |
| シーケンス | array                              | <array>           | C++11 | 固定長の配列                |
| シーケンス | forward\_list                      | <forward\_list>   | C++11 | 単方向連結リスト              |
| シーケンス | inplace\_vector                    | <inplace\_vector> | C++26 | 容量固定・オブジェクト内に格納       |
| シーケンス | hive                               | <hive>            | C++26 | 複数の要素ブロック             |
| 連想    | set／multiset                       | <set>             | C++98 | 典型的には平衡二分木            |
| 連想    | map／multimap                       | <map>             | C++98 | 典型的には平衡二分木            |
| 非順序連想 | unordered\_set／unordered\_multiset | <unordered\_set>  | C++11 | ハッシュ表                 |
| 非順序連想 | unordered\_map／unordered\_multimap | <unordered\_map>  | C++11 | ハッシュ表                 |
| アダプタ  | stack                              | <stack>           | C++98 | 既定で deque をラップ        |
| アダプタ  | queue／priority\_queue              | <queue>           | C++98 | 既定で deque／vector をラップ |
| アダプタ  | flat\_map／flat\_multimap           | <flat\_map>       | C++23 | ソート済みのキー列と値列          |
| アダプタ  | flat\_set／flat\_multiset           | <flat\_set>       | C++23 | ソート済みの配列              |

`std::string` も要素を連続して持ちコンテナの要件を満たしますが、規格上は第27章の文字列ライブラリに属するため表には入れていません。`multi` が付く型は同じキーを複数持てる版です。

### コンテナアダプタ（stack・queue・priority\_queue）の位置づけ

stack・queue・priority\_queue は、別のコンテナを内部に持ち、操作を「後入れ先出し」「先入れ先出し」「優先度順」に絞ったラッパーです。イテレータを持たないので範囲 for 文では回せません（同じアダプタでも flat\_map・flat\_set はイテレータを持ちます）。スタックとキューの考え方そのものは[スタックとキューの違いの記事](/tech/details/4587/)で解説しています。priority\_queue は既定で最大値が先頭に来るため、最小値から取り出したいときは比較関数に `std::greater` を渡します。

```
#include <functional>
#include <iostream>
#include <queue>
#include <vector>
int main() {
    std::priority_queue<int> maxq;  // 既定は大きい順
    std::priority_queue<int, std::vector<int>, std::greater<int>> minq;  // 小さい順
    for (int x : {5, 1, 8, 3}) { maxq.push(x); minq.push(x); }
    std::cout << maxq.top() << ' ' << minq.top() << '\n';  // 8 1
}
```

## 計算量とメモリ使用量の比較：コンテナ横断の一覧

どのコンテナを選ぶかは、よく使う操作の計算量と、要素1つあたりに余分にかかるメモリでおおむね決まります。計算量は規格が要件として定め、メモリは実装ごとに違います。

### 操作ごとの計算量

規格の各節（[\[sequence.reqmts\]](https://eel.is/c++draft/sequence.reqmts)・[\[associative.reqmts.general\]](https://eel.is/c++draft/associative.reqmts.general)・[\[unord.req.general\]](https://eel.is/c++draft/unord.req.general) ほか）が定める計算量をまとめると次のとおりです。nは要素数、「－」はその操作を持たないことを表します。

| コンテナ                | 添字アクセス | 末尾に追加 | 先頭に追加 | 挿入・削除            | 値・キーの検索   |
| ------------------- | ------ | ----- | ----- | ---------------- | --------- |
| vector              | 定数     | 償却定数  | 線形    | 線形               | 線形        |
| deque               | 定数     | 定数    | 定数    | 線形               | 線形        |
| list                | －      | 定数    | 定数    | 定数（位置が既知）        | 線形        |
| forward\_list       | －      | －     | 定数    | 定数（位置が既知）        | 線形        |
| array               | 定数     | －     | －     | －                | 線形        |
| set・map             | －      | －     | －     | 対数（位置指定の削除は償却定数） | 対数        |
| unordered\_set・map  | －      | －     | －     | 平均定数・最悪線形        | 平均定数・最悪線形 |
| flat\_set・flat\_map | －      | －     | －     | 線形               | 対数        |

シーケンスコンテナの「挿入・削除」は途中の位置に対するものです。vector の「償却定数」は、ときどき起きる再確保の費用を全体でならすと1回あたり定数になるという意味です。map の `operator[]` はキーで引く操作で、添字アクセスとは別物なので表では「－」としています。unordered 系の最悪線形は、多数のキーが同じバケットに集まったときに起きます。

### 1要素あたりのメモリ使用量の実測

計算量の表には現れない差がメモリです。要素の確保をすべて数える自作アロケータを各コンテナに渡し、int（map 系は int→int）を10万個入れた時点でアロケータに要求されているバイト数を、Apple clang 21（libc++ 21・x86\_64・`-std=c++23 -O2`）で2026年10月15日に測りました。malloc が内部で持つ管理領域や切り上げ分は含みません。

| コンテナ               | 合計（バイト）   | 1要素あたり | 確保の回数   |
| ------------------ | --------- | ------ | ------- |
| vector（reserve あり） | 400,000   | 4.00   | 1       |
| deque              | 402,432   | 4.02   | 106     |
| vector（reserve なし） | 524,288   | 5.24   | 18      |
| flat\_map          | 1,048,576 | 10.49  | 36      |
| forward\_list      | 1,600,000 | 16.00  | 100,000 |
| list               | 2,400,000 | 24.00  | 100,000 |
| set                | 3,200,000 | 32.00  | 100,000 |
| unordered\_map     | 3,223,016 | 32.23  | 100,016 |
| map                | 4,000,000 | 40.00  | 100,000 |

4バイトの int を入れても、list は前後のポインタ2本を足した24バイトのノードを要素ごとに確保し、map は木をたどるポインタと色情報を足した40バイトのノードになります。unordered\_map は24バイトのノードに加え、10万2,877個のバケット（各8バイト）を持っていました。reserve なしの vector が5.24バイトなのは、容量が2倍ずつ伸びて131,072要素分を確保していたためで、確保の回数も18回あります。flat\_map はキー列と値列の2本の vector なので、ノード型の map の約4分の1で済みました。

要素が小さい型（int や double など）では、ノード型（list・map・set・unordered 系）は要素の本体よりポインタのほうが大きくなり、要素数が数十万を超えると差は数MB単位になります。メモリを抑えたいときは、まず vector・deque・flat\_map に置き換えられないかを検討してください。ノードが要素ごとにヒープへ個別に確保されるという点は、[Rustのスタックとヒープの記事](/tech/details/2814/)で扱う考え方と同じです。

## vectorの使い方：可変長配列の宣言・初期化・結合・サイズ管理

C++で「可変長配列」が必要なときの第一候補が `std::vector` です。要素数（`size()`）と確保済みの容量（`capacity()`）を別々に管理し、容量が足りなくなると自動で領域を取り直します。次の例は、Apple clang 21（libc++）の `-std=c++23` でコンパイルし、コメントの出力を確認したものです。

```
#include <iostream>
#include <vector>
int main() {
    std::vector<int> v;          // 要素数0の可変長配列
    v.reserve(100);              // 容量だけ先に確保（size は 0 のまま）
    for (int i = 0; i < 5; ++i) v.push_back(i * 10);
    std::cout << v.size() << ' ' << v.capacity() << '\n';  // 5 100
    std::vector<int> tail{50, 60};
    v.insert(v.end(), tail.begin(), tail.end());  // 結合（C++98から使える書き方）
    v.append_range(tail);                         // 結合（C++23）
    std::cout << v.size() << ' ' << v[1] << ' ' << v.at(6) << '\n';  // 9 10 60
    v.resize(3);                 // 要素数を3に切り詰める
    for (int x : v) std::cout << x << ' ';  // 0 10 20
    std::cout << '\n';
}
```

添字の `v[i]` は範囲外でも検査しないため未定義動作になり、`at(i)` は範囲外で `std::out_of_range` を投げます。`append_range` は C++23 の追加で、古いコンパイラでは `insert` を使います。

### 初期化の書き方と v(5)・v{5} の違い

コンテナは `{}` の中に要素を並べて初期化できます（C++11の初期化子リスト）。注意が要るのは、丸括弧と波括弧で意味が変わる場合です。波括弧で初期化すると、規格の [\[over.match.list\]](https://eel.is/c++draft/over.match.list) により `std::initializer_list` を受け取るコンストラクタが優先されるため、`std::vector<int> v{5}` は「要素数5」ではなく「値5の要素1つ」になります。

```
#include <iostream>
#include <map>
#include <numeric>
#include <string>
#include <vector>
int main() {
    std::vector<int> a(5);       // 要素数5（すべて0）
    std::vector<int> b{5};       // 要素1つ（値は5）
    std::vector<int> c(3, 7);    // 要素数3（すべて7）
    std::vector<int> d{3, 7};    // 要素2つ（3と7）
    std::cout << a.size() << ' ' << b.size() << ' ' << c.size() << ' ' << d.size() << '\n';  // 5 1 3 2
    std::vector<int> e(5);
    std::iota(e.begin(), e.end(), 1);  // 1, 2, 3, 4, 5 の連番
    std::map<std::string, int> m{{"apple", 3}, {"banana", 5}};  // map も {} で初期化できる
    std::cout << e.back() << ' ' << m.size() << '\n';  // 5 2
}
```

要素数を指定したいときは丸括弧、要素の値を並べたいときは波括弧、と使い分けます。連番は `std::iota`、同じ値で埋め直すなら `std::fill` を使います。

### capacityの伸び方とreserveで再確保を減らす方法

容量を超えて `push_back` すると、新しい領域を確保して全要素をムーブ（またはコピー）し直します。規格は伸び率を定めておらず、実装によって違います。libc++ で1要素ずつ200個追加したときの容量は `1 2 4 8 16 32 64 128 256` と2倍ずつ増えました。GCCのlibstdc++も「現在の要素数＋max(現在の要素数, 追加数)」で新しい容量を計算するので、1要素ずつなら同じく2倍です。[Microsoft STLのvector実装](https://github.com/microsoft/STL/blob/main/stl/inc/vector)は旧容量の1.5倍（旧容量＋旧容量÷2）を候補にしており、同じコードでも再確保の回数が変わります。

要素数の上限が分かる場合は、先に `reserve(n)` を呼ぶと、追加した要素数がその容量を超えるまで再確保が起きません（`reserve` の呼び出し自体では再確保が起こりえます）。`reserve` は容量だけを確保し、`size()` は変わりません。要素を実際に作りたい場合は `resize(n)` です。

### vector・list・dequeの速度差の実測

計算量の表だけを見ると、途中挿入は list が有利に見えます。ただし挿入位置まではリストを先頭からたどる必要があり、要素がメモリ上に散らばる list はこの走査が遅くなります。20,000個の int を毎回ランダムな位置へ挿入する処理を、Intel Core i9-9880H・Apple clang 21・`-O2` で3回計測したところ（2026年10月15日）、vector が8.9〜9.6ミリ秒、list が2,062〜2,112ミリ秒でした（挿入位置は `std::mt19937`・シード42で事前に生成し、list は毎回 `begin()` から `std::advance` で進める条件）。vector は挿入のたびに後ろの要素をずらしますが、それでも200倍以上速い結果です。差の内訳（位置探索とノード確保）は切り分けていません。

末尾への追加だけなら deque も候補になります。同じ環境で int を1,000万個 `push_back` すると、reserve なしの vector が172〜200ミリ秒、deque が73〜93ミリ秒、reserve した vector が14〜16ミリ秒でした。ただし作成済みの1,000万要素を範囲 for で10周する走査は、vector の83〜99ミリ秒に対し deque は349〜354ミリ秒かかりました。追加が多く走査が少ないなら deque、要素数が事前に分かるか走査が多いなら vector、と分けられます。

list を選ぶ根拠になるのは、「要素のアドレスが変わってはいけない」「`splice` で別リストへ要素を付け替えたい」といった要件がある場合です。挿入位置を毎回先頭から探す使い方なら、速度を理由に list を選ぶべきではありません。挿入位置のイテレータを保持できる場合や要素のムーブが重い型では、条件を変えて測り直してください。連続した要素をまとめて処理する最適化の仕組みは[SIMDと自動ベクトル化の記事](/tech/details/18123/)で扱っています。

## mapとunordered\_mapの使い方：キーと値を扱う辞書型

PythonのdictやC#のDictionaryにあたる「キーと値の組（key-value）」は、C++では `std::map` か `std::unordered_map` で扱います。書き方はほぼ共通ですが、`operator[]` の挙動だけは必ず押さえてください。

```
#include <iostream>
#include <map>
#include <string>
#include <unordered_map>
int main() {
    std::map<std::string, int> stock{{"apple", 3}, {"banana", 5}};
    // operator[] は存在しないキーを値0で挿入してしまう
    std::cout << stock["cherry"] << ' ' << stock.size() << '\n';  // 0 3
    // 読むだけなら contains（C++20）か find を使う
    if (stock.contains("durian")) std::cout << "found\n";
    std::cout << stock.size() << '\n';  // 3 のまま
    // 既にあるキーは上書きしない（C++17）
    auto [it, inserted] = stock.try_emplace("apple", 100);
    std::cout << it->second << ' ' << inserted << '\n';  // 3 0
    // キー順に並ぶ（map）
    for (const auto& [name, qty] : stock) std::cout << name << '=' << qty << ' ';  // apple=3 banana=5 cherry=0
    std::cout << '\n';
    std::unordered_map<std::string, int> hits;
    hits.reserve(1000);  // 1000要素まで再ハッシュしないバケット数を確保
    hits["x"]++;
    std::cout << hits.bucket_count() << '\n';  // libc++では1009（値は実装依存）
}
```

`stock["cherry"]` は読み取りのつもりでも、キーが無ければ値を初期化した要素を挿入します。存在確認は `contains`（C++20）か `find`、値の取得は `at`（キーが無ければ例外）を使うと、意図しない挿入を防げます。map に `push_back` は無く、追加は `insert`・`emplace`・`try_emplace` で行います。

### mapとunordered\_mapの選び方と検索速度

map はキーを比較して木構造に並べるため、走査するとキー順に出てきます。`lower_bound` で「ある値以上の最初のキー」を探すような範囲検索もできます。unordered\_map はハッシュ値でバケットに振り分けるので、順序は保証されませんが平均では定数時間で引けます。100万個のランダムな int キーを入れて全件を検索した実測（前節と同じ環境・3回・2026年10月15日、unordered\_map は事前に reserve）は、map が918〜1,122ミリ秒、unordered\_map が115〜146ミリ秒でした。

キー順が不要なら unordered\_map、順序付きの出力や範囲検索が要るなら map、という分け方で足ります。順序付きの木とハッシュ表の使い分けは他の言語でも同じで、Javaでの実行結果は[TreeSetとHashSetの違いの記事](/tech/details/4046/)で比べています。

### 文字列キーの検索で一時オブジェクトを作らない方法

`std::map<std::string, int>` の `find` に文字列リテラル（`const char*`）を渡すと、引数がいったん `std::string` に変換されます（`std::string_view` は暗黙に変換されないため、既定の比較関数のままでは `find` に渡すとコンパイルエラーになります）。キーがSSO（短い文字列を内部に持つ最適化）に収まらない長さ（libc++では23文字以上）なら、検索のたびにヒープ確保が起きます。比較関数に `std::less<>` を指定すると、C++14の異種ルックアップ（[比較関数が is\_transparent を持つ場合](https://eel.is/c++draft/associative.reqmts.general)）が働き、変換せずにそのまま比較します。unordered\_map では C++20 から、ハッシュ関数と等価判定の**両方**が `is_transparent` を持つときに同じことができます。

```
#include <functional>
#include <iostream>
#include <map>
#include <string>
#include <string_view>
#include <unordered_map>
struct SvHash {
    using is_transparent = void;  // string_view などでの検索を許可する印
    std::size_t operator()(std::string_view s) const noexcept {
        return std::hash<std::string_view>{}(s);
    }
};
int main() {
    std::map<std::string, int, std::less<>> m{{"timeout", 30}};  // std::less<> で透過比較
    std::unordered_map<std::string, int, SvHash, std::equal_to<>> u{{"timeout", 30}};
    std::string_view key = "timeout";
    std::cout << m.find(key)->second << ' ' << u.find(key)->second << '\n';  // 30 30
}
```

37文字のキーで `find` を1,000回呼び、グローバルの `operator new` の呼び出し回数を数えたところ、既定の `std::map<std::string, int>` に文字列リテラルを渡した場合は1,000回、`std::less<>` 版と透過ハッシュの unordered\_map に `string_view` を渡した場合は0回でした（libc++ 21）。設定値や識別子のように文字列キーを何度も引くコードでは、宣言を変えるだけで確保が無くなります。

### reserveがあるコンテナ・無いコンテナ

deque や map で容量を先に確保したくても、どちらにも `reserve` はありません。libc++で `std::deque<int>` と `std::map<int,int>` に `reserve(10)` を書くと、`no member named 'reserve'` でコンパイルエラーになります。

| コンテナ                     | reserve | 意味                    |
| ------------------------ | ------- | --------------------- |
| vector・string            | あり      | 容量（要素を置ける領域）を確保       |
| unordered 系              | あり      | n要素まで再ハッシュしないバケット数を確保 |
| hive（C++26）              | あり      | 予備の要素ブロックを確保          |
| inplace\_vector（C++26）   | あり      | 何もしない（容量超えは例外）        |
| deque・list・forward\_list | なし      | 要素ごと・ブロックごとに確保        |
| map・set 系                | なし      | ノードごとに確保              |

unordered\_map の `reserve(1000)` の後、libc++ では `bucket_count()` が1009になりました（この条件で選ばれた値で、バケット数の決め方は実装ごとに異なります）。map や deque で確保回数を減らしたい場合は、コンテナを vector に変えるか、アロケータを差し替える方向で考えます。

## 要素型に求められる条件：比較・ハッシュ・noexceptのムーブ

自作の構造体をコンテナに入れるとき、コンテナの種類によって型に必要なものが変わります。足りないとコンパイルエラーになるか、エラーにならずに性能や正しさが落ちます。

- **map・set のキー**：比較関数（既定は `operator<`）が「狭義の弱順序」を満たすこと。`a < b` と `b < a` がどちらも偽なら同じキーとみなされるので、比較に使わないメンバだけが違う値を後から挿入しても入らず、既存の要素が残ります（`insert` の戻り値の bool が false になります）。
- **unordered 系のキー**：`std::hash` の特殊化かハッシュ関数オブジェクトと、等価判定（既定は `operator==`）。等価と判定されるキーには同じハッシュ値を返す必要があります。
- **vector の要素**：ムーブコンストラクタに `noexcept` を付けること。付いていないとエラーにはなりませんが、再確保のたびにコピーされます（deque は両端への追加で既存の要素を移さないため、この影響を受けません）。

3つ目は気づきにくい差です。[\[vector.modifiers\]](https://eel.is/c++draft/vector.modifiers) は、末尾に1要素を挿入する途中で例外が出ても、要素型がコピー挿入可能か、ムーブが例外を投げない（`is_nothrow_move_constructible_v<T>` が真）なら何も変化しないこと（強い例外保証）を求めています。要素を移している途中でムーブが例外を投げると元に戻せないため、実装は `std::move_if_noexcept` と同じ判定で、ムーブが例外を投げない型とコピーできない型はムーブし、それ以外（コピーでき、ムーブが例外を投げうる型）はコピーします。

```
#include <iostream>
#include <vector>
struct Plain {  // ムーブコンストラクタに noexcept が無い
    static inline int copies = 0, moves = 0;
    Plain() = default;
    Plain(const Plain&) { ++copies; }
    Plain(Plain&&) { ++moves; }
};
struct Safe {   // ムーブコンストラクタが noexcept
    static inline int copies = 0, moves = 0;
    Safe() = default;
    Safe(const Safe&) { ++copies; }
    Safe(Safe&&) noexcept { ++moves; }
};
int main() {
    std::vector<Plain> a;
    std::vector<Safe> b;
    for (int i = 0; i < 1000; ++i) { a.emplace_back(); b.emplace_back(); }
    std::cout << Plain::copies << ' ' << Plain::moves << '\n';  // 1023 0（再確保のたびにコピー）
    std::cout << Safe::copies << ' ' << Safe::moves << '\n';    // 0 1023（再確保のたびにムーブ）
}
```

1,000個追加する間に容量は1から1024まで2倍ずつ伸び、要素を移し替える再確保が10回起きます。その都度既存の要素（1＋2＋…＋512個）を移すので、合計1,023回の移動になります。`noexcept` が無い型ではその1,023回がすべてコピーでした。std::string や vector を抱える型でコピーが走ると、確保とコピーがそのまま積み上がります。ムーブコンストラクタを自分で書くなら `noexcept` を付け、書かずにコンパイラ生成に任せられるなら任せるのが確実です。デストラクタやコピーコンストラクタを自分で宣言すると、ムーブコンストラクタは暗黙には生成されず、移動はコピーに落ちる点にも注意してください。

## イテレータの無効化：eraseとpush\_backで壊れる条件

コンテナを変更すると、それまで持っていたイテレータ・ポインタ・参照が使えなくなる（無効化される）ことがあります。無効化されたイテレータを使うと未定義動作で、クラッシュせずに誤った値を返すこともあるため、発見が遅れやすい不具合です。クラッシュした場合の原因の調べ方は[セグメンテーションフォルトの原因と調べ方の記事](/tech/details/7982/)で扱っています。

| 操作                   | 無効になるもの            | 規格の節                  |
| -------------------- | ------------------ | --------------------- |
| vector の挿入で容量超過      | 全要素のイテレータ・ポインタ・参照  | \[vector.modifiers\]  |
| vector の erase       | 削除位置以降のイテレータ・参照    | \[vector.modifiers\]  |
| deque の途中挿入          | 全イテレータと全参照         | \[deque.modifiers\]   |
| deque の両端への挿入        | 全イテレータ（参照は有効）      | \[deque.modifiers\]   |
| list・map の erase     | 削除した要素のものだけ        | \[list.modifiers\] ほか |
| list・map の挿入         | 無効になるものはない         | \[list.modifiers\] ほか |
| unordered 系の挿入で再ハッシュ | 全イテレータ（参照・ポインタは有効） | \[unord.req.general\] |

ループの中で要素を消すときは、`erase` の戻り値（削除した要素の次を指すイテレータ）で進めます。C++20以降は `std::erase_if` で1行にまとめられます。

```
#include <iostream>
#include <vector>
int main() {
    std::vector<int> v{1, 2, 3, 4, 5, 6};
    // ループ中の erase は戻り値のイテレータで進める
    for (auto it = v.begin(); it != v.end();) {
        if (*it % 2 == 0) it = v.erase(it);
        else ++it;
    }
    for (int x : v) std::cout << x << ' ';  // 1 3 5
    std::cout << '\n';
    // C++20 以降は std::erase_if で1行
    std::vector<int> w{1, 2, 3, 4, 5, 6};
    auto n = std::erase_if(w, [](int x) { return x % 2 == 0; });
    std::cout << n << ' ' << w.size() << '\n';  // 3 3
    // 容量を超える push_back は再確保＝既存のポインタ・参照・イテレータが無効になる
    std::vector<int> a{1, 2, 3};
    a.resize(a.capacity());  // 容量ちょうどまで要素を入れる
    const auto old_capacity = a.capacity();
    a.push_back(4);
    std::cout << (old_capacity < a.capacity() ? "reallocated" : "same") << '\n';
}
```

最後の例では、容量ちょうどまで要素を入れた vector に1要素追加し、`capacity()` が増えたこと（＝再確保が起きたこと）を確かめています。この時点で既存要素へのポインタ・参照・イテレータはすべて無効です。末尾追加だけなら `reserve` で防げますが、途中の挿入・削除は再確保が無くても挿入位置以降を無効にします。途中を変更しても他の要素へのポインタを保ちたいなら list・hive を検討します。

## C++23・C++26で増えたコンテナ：flat\_map・inplace\_vector・hive

C++23で flat 系の4種（flat\_map・flat\_multimap・flat\_set・flat\_multiset）が、C++26で inplace\_vector と hive が加わります。C++26は、WG21（ISOのC++委員会）が2026年3月のCroydon会合で技術作業を終え、2026年10月15日時点では[ISOの国際規格案（DIS）の照会段階](https://www.iso.org/standard/91179.html)にあり、ISO/IEC 14882:2026としてはまだ出版されていません（出版済みの最新版は2024年10月のISO/IEC 14882:2024）。正式な出版前ですが、GCCとClangは `-std=c++26` で先行実装を使えます。

### flat\_map・flat\_set（C++23）：ソート済み配列の連想コンテナ

flat\_map は map に似た検索・追加のインターフェースを持ちつつ、中身はキーの配列と値の配列（既定はどちらも vector）をソートしておくコンテナアダプタです。規格は、1要素の挿入・削除が線形時間であること、map のイテレータ無効化の規則やノードハンドル（`extract` など）の要件を満たさないことを明記しており、map からそのまま置き換えられるわけではありません。

```
#include <flat_map>
#include <iostream>
#include <string>
int main() {
    std::flat_map<int, std::string> codes;
    codes.emplace(404, "Not Found");
    codes.emplace(200, "OK");
    codes.emplace(301, "Moved Permanently");
    // キーと値は別々の連続領域（既定は std::vector）に並ぶ
    for (int k : codes.keys()) std::cout << k << ' ';  // 200 301 404
    std::cout << '\n' << codes.at(301) << '\n';
}
```

100万件のキーを一括でソートして `std::sorted_unique` 付きで構築した場合、構築は119〜132ミリ秒（map は1,735〜1,814ミリ秒）、全件の検索は478〜498ミリ秒で、map の約半分でした（2026年10月15日・前述と同じ環境）。一方、1件ずつの挿入は後ろの要素をずらすので、要素数が多いと map より遅くなります。**作成後はほぼ読むだけの設定表や辞書**に向き、頻繁に追加・削除する用途には向きません。flat\_mapを使えるのは、GCC 15.1のlibstdc++（C++23対応は実験的扱い）、LLVMのlibc++ 20（flat\_setはlibc++ 21）、MSVC Build Tools 14.51（Visual Studio 2026 18.6で出荷）以降です。

### inplace\_vector・hive（C++26）：容量固定の配列とアドレスが動かないコンテナ

`inplace_vector<T, N>` は、容量 N を型に持ち、要素をオブジェクト自身の内部に置く可変長配列です。ローカル変数として置けば、要素のための動的確保は起きません。`push_back` や `resize` などで N を超えると `std::bad_alloc` を投げ、`try_push_back` は満杯なら空の `std::optional`（`std::nullopt`）を返し、`unchecked_push_back` は空きがあることを呼び出し側の前提条件とします。動的確保を避けたい組み込みやゲームのループ内で、「最大数は分かっているが個数は変わる」データに使います。

`hive` は、要素を複数のメモリブロックに置き、挿入と削除を定数時間で行うシーケンスコンテナです。削除しても後ろの要素を詰めず、空いた場所を後の挿入で再利用します。挿入位置はコンテナが決めるため順序は保てませんが、要素のアドレスが動かないので、ゲームの弾やパーティクルのように大量に生成・消滅するオブジェクトの管理に向きます。2026年10月15日時点で、inplace\_vectorは[GCC 16のlibstdc++](https://gcc.gnu.org/gcc-16/changes.html)（2026年4月30日公開の16.1から。C++26対応は実験的扱い）に入っていますが、[libc++のC++26ステータス表](https://libcxx.llvm.org/Status/Cxx26.html)（2026年10月15日参照）とMicrosoft STLの追跡Issueでは未実装です。hiveはGCC 16のリリースノートに記載がなく、libc++とMicrosoft STLでも実装済みの版は示されていません（手元のlibc++ 21では機能テストマクロ `__cpp_lib_hive` も未定義でした）。今使うなら提案者Matt Bentleyの参照実装[plf::hive](https://github.com/mattreecebentley/plf%5Fhive)を導入します。

### span・mdspan：要素を所有しないビューの用途

`std::span`（C++20）と `std::mdspan`（C++23）は第23章に含まれますが、要素を所有しない「ビュー」です。span は vector や配列など連続した1次元の要素列を、コピーせずに参照して関数へ渡すときに使います。mdspan は多次元の参照で、各次元の大きさ（extents）、添字からメモリ位置への対応（レイアウト）、要素へのアクセス方法を型で持ちます。元のコンテナが再確保されるとビューも無効になるため、保持期間は元のコンテナより短くします。

## コンテナの選び方：用途別の判断基準

まず必要な操作（キー検索・集合・キュー・順番に並べるだけ）で候補を絞り、そのうえで要素数が固定か、順序が要るか、要素のアドレスを保つ必要があるかを確認します。条件が複数当てはまる場合は、それらを同時に満たすものを選びます。

- 要素数がコンパイル時に決まる → **array**（上限だけ決まるなら C++26 の inplace\_vector）
- キーで値を引く → 順序不要なら **unordered\_map**、キー順・範囲検索が要るなら **map**、構築後に読むだけなら **flat\_map**
- 重複を除いた集合 → 同じ基準で **unordered\_set／set／flat\_set**
- 先頭と末尾の両方に出し入れする → **deque**（キューとして使うだけなら **queue**）
- 優先度の高いものから取り出す → **priority\_queue**
- 要素のアドレスを保持し続ける必要がある → **list**（C++26 環境なら hive）
- 上のどれでもない → **vector**

判断を誤りやすいのは、「途中挿入が多いから list」「キー検索だからとりあえず map」の2つです。前述の測定では、位置探索込みの20,000回の挿入で vector が、100万個のキーの検索で unordered\_map が大差で速く、メモリも list・map は要素の数倍を消費しました。要素数を変えた測定はしていないので、差が逆転する境界は分かりません。性能で迷ったときは計算量の表ではなく、実際のデータ量で計測して決めてください。C++でゲームやGUIを作る場合の環境としては、[無料のC++フレームワークSiv3Dの記事](/tech/details/9701/)も参考になります。

## よくある質問

### C++で辞書型（key-value）を使うにはどうすればよいですか？

`std::unordered_map<キー型, 値型>` を使います。キー順に並べたい場合は `std::map` です。`#include <unordered_map>`（map なら `<map>`）を書き、`m["key"] = 値;` で追加・更新できます。

### STLとC++標準ライブラリは違うものですか？

STLは、C++98で標準に取り込まれたテンプレートライブラリに由来する通称です。現在の規格にSTLという区分はなく、コンテナ・イテレータ・アルゴリズムはC++標準ライブラリの一部として定義されています。

### vectorの要素数とサイズを調べるには？

要素数は `size()`、確保済みの容量は `capacity()` で取得します。空かどうかは `empty()` で判定します。`size() * sizeof(要素型)` は要素そのもののバイト数で、未使用の容量や要素内部の動的確保は含みません（`vector<bool>` はビット単位で詰める特殊化なので、この式は当てはまりません）。

### vectorどうしを結合するには？

`a.insert(a.end(), b.begin(), b.end());` で a の末尾に b を連結できます。C++23 に対応したコンパイラなら `a.append_range(b);` と書けます。

### arrayとvectorはどう使い分けますか？

要素数がコンパイル時に決まり変わらないなら array、実行時に増減するなら vector です。array は要素を自身の内部に持ち、要素数が型の一部になります（vector のように要素用の領域を別に確保しません）。

## 関連記事

- [メタプログラミングとは？仕組みとRuby・Python・C++での実装例をわかりやすく解説](/tech/details/3510/)
- [ゼロコスト抽象化とは？Rustで抽象度と実行速度を両立する仕組み](/tech/details/4440/)
- [Rustのスタックとヒープ｜値がヒープへ脱出する条件とメモリ領域の使い分け](/tech/details/2814/)
- [【Java】TreeSetとHashSetの違いと使い分け｜順序・速度・null・範囲検索を実行結果で解説](/tech/details/4046/)
- [セグメンテーションフォルト（Segmentation fault）の原因と調べ方｜core dumpedの意味・gdb・ASan](/tech/details/7982/)

---

出典: [C++のコンテナクラスとは？種類一覧とvector・mapの使い分け【C++23・C++26対応】](<https://www.issoh.co.jp/tech/details/5081/>)（株式会社一創）
