19 人が閲覧(直近 30 日) プログラミング言語

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

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> も同じクラステンプレートから生成されます。型を引数に取る設計は他言語のジェネリクスにあたり、言語ごとの違いはジェネリクスの解説記事で比較しています。

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

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

C++ワーキングドラフトの [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 はイテレータを持ちます)。スタックとキューの考え方そのものはスタックとキューの違いの記事で解説しています。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]・[associative.reqmts.general]・[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のスタックとヒープの記事で扱う考え方と同じです。

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] により 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実装は旧容量の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と自動ベクトル化の記事で扱っています。

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の違いの記事で比べています。

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

std::map<std::string, int> の find に文字列リテラル(const char*)を渡すと、引数がいったん std::string に変換されます(std::string_view は暗黙に変換されないため、既定の比較関数のままでは find に渡すとコンパイルエラーになります)。キーがSSO(短い文字列を内部に持つ最適化)に収まらない長さ(libc++では23文字以上)なら、検索のたびにヒープ確保が起きます。比較関数に std::less<> を指定すると、C++14の異種ルックアップ(比較関数が is_transparent を持つ場合)が働き、変換せずにそのまま比較します。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] は、末尾に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で壊れる条件

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

操作 無効になるもの 規格の節
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)の照会段階にあり、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++(2026年4月30日公開の16.1から。C++26対応は実験的扱い)に入っていますが、libc++のC++26ステータス表(2026年10月15日参照)とMicrosoft STLの追跡Issueでは未実装です。hiveはGCC 16のリリースノートに記載がなく、libc++とMicrosoft STLでも実装済みの版は示されていません(手元のlibc++ 21では機能テストマクロ __cpp_lib_hive も未定義でした)。今使うなら提案者Matt Bentleyの参照実装plf::hiveを導入します。

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の記事も参考になります。

よくある質問

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 のように要素用の領域を別に確保しません)。

関連記事

お気に入りに入れた記事の一覧

この記事は以下の記事からリンクされています

資料請求

今日のトレンド記事 直近 24 時間で、いつもより多く読まれている記事

  1. 2026.10.27 テックブログ Brotli 脆弱性:CVE-2026-111497の影響確認とNode.js・Python・.NETの更新手順
  2. 2025.02.27 コラム トラッキングビーコン(Webビーコン)とは?仕組み・用途・確認と対策を徹底解説
  3. 2026.10.27 テックブログ Joshin(ジョーシン)不正アクセス:最大600万件の対象とサブスク初期パスワードの対処
  4. 2026.10.23 テックブログ Renta!はいつ再開?不正アクセスでメールアドレス等が流出したおそれ・再開時のパスワード再設定と運営側の対応
  5. 2026.07.09 テックブログ Muse Imageとは?Metaの画像生成AIの使い方・API料金(1枚0.01ドル)と日本での提供状況【2026年10月】

RELATED POSTS 関連記事

目次