std::map / std::unordered_map
『C++』の標準ライブラリには、キーと値のペアを管理するための連想コンテナとして std::map と std::unordered_map が用意されています。map はキーを自動的にソートして管理する順序付きコンテナで、unordered_map はハッシュテーブルを使って高速なアクセスを実現する非順序コンテナです。用途に応じて使い分けることで、効率的なデータ管理が実現できます。
構文
// ========================================
// map / unordered_map の基本構文
// ========================================
#include <map>
#include <unordered_map>
#include <string>
// --- std::map(キーがソートされます) ---
std::map<std::string, int> m;
// 要素の挿入(複数の方法があります)
m["シンジ"] = 14;
m.insert(std::make_pair("レイ", 14));
m.emplace("アスカ", 14);
// 要素へのアクセス
int val = m["シンジ"]; // キーが存在しなければデフォルト値で作成します
int val2 = m.at("シンジ"); // キーが存在しなければ例外 std::out_of_range を投げます
// 要素の検索
if (m.count("レイ") > 0) { /* キーが存在します */ }
auto it = m.find("アスカ");
if (it != m.end()) {
// it->first がキー、it->second が値です
}
// 要素の削除
m.erase("シンジ"); // キーを指定して削除します
m.erase(m.begin()); // イテレータで削除します
m.clear(); // すべての要素を削除します
// サイズ確認
int sz = m.size();
bool empty = m.empty();
// --- std::unordered_map(ハッシュベース、ソートなし) ---
std::unordered_map<std::string, int> um;
um["カヲル"] = 15;
um["ミサト"] = 29;
// 操作は map とほぼ同じです
// ただしキーの順序は保証されません
構文一覧
| 構文・メソッド | 概要 |
|---|---|
| map<K, V> / unordered_map<K, V> | キー型 K、値型 V の連想コンテナを宣言します。map はキーがソートされ、unordered_map はハッシュテーブルを使います。 |
| m[key] = value | キーに値を代入します。キーが存在しなければ新しく要素を作成します。 |
| m.at(key) | キーに対応する値を返します。キーが存在しない場合は std::out_of_range 例外を投げます。 |
| m.insert(make_pair(k, v)) | キーと値のペアを挿入します。すでにキーが存在する場合は挿入されません。 |
| m.emplace(k, v) | 引数をその場でコンストラクトして挿入します。コピーコストを避けられます。 |
| m.find(key) | キーを検索してイテレータを返します。見つからない場合は m.end() を返します。 |
| m.count(key) | キーが存在する場合は 1、存在しない場合は 0 を返します(map は重複キーを持ちません)。 |
| m.erase(key) | 指定したキーの要素を削除します。 |
| m.size() / m.empty() | 要素数を返します / 空かどうかを返します。 |
| m.clear() | すべての要素を削除します。 |
| イテレータ it->first / it->second | イテレータが指すペアのキーと値にアクセスします。 |
| 計算量の違い | map の検索・挿入・削除は O(log n)、unordered_map は平均 O(1) です。 |
サンプルコード
eva_map.cpp
// ========================================
// eva_map.cpp
// エヴァンゲリオンのパイロット情報を
// std::map と std::unordered_map で管理するサンプルです
// ========================================
#include <iostream>
#include <map>
#include <unordered_map>
#include <string>
int main() {
// ========================================
// 1. std::map の基本操作(キーはソートされます)
// ========================================
std::cout << "=== std::map:パイロットの搭乗機番号 ===" << std::endl;
std::map<std::string, int> pilotUnit;
// 要素の挿入(複数の方法があります)
pilotUnit["碇シンジ"] = 1; // [] 演算子で挿入します
pilotUnit["綾波レイ"] = 0; // 零号機(プロト型)です
pilotUnit.insert(std::make_pair("惣流・アスカ・ラングレー", 2)); // insert で挿入します
pilotUnit.emplace("渚カヲル", 2); // 弐号機に搭乗します(emplace で挿入)
// map はキーがアルファベット順(辞書順)にソートされます
std::cout << "--- 全パイロット一覧(ソート済み)---" << std::endl;
for (auto it = pilotUnit.begin(); it != pilotUnit.end(); ++it) {
std::cout << " " << it->first << " : 第" << it->second << "号機" << std::endl;
}
// 要素へのアクセス
std::cout << std::endl;
std::cout << "碇シンジの搭乗機: 第" << pilotUnit["碇シンジ"] << "号機" << std::endl;
std::cout << "綾波レイの搭乗機: 第" << pilotUnit.at("綾波レイ") << "号機(零号機)" << std::endl;
// 要素の検索(find を使います)
std::cout << std::endl;
std::string target = "惣流・アスカ・ラングレー";
auto it = pilotUnit.find(target);
if (it != pilotUnit.end()) {
std::cout << target << " は第" << it->second << "号機のパイロットです。" << std::endl;
}
// 存在しないキーの検索
if (pilotUnit.count("葛城ミサト") == 0) {
std::cout << "葛城ミサトはパイロットではありません(作戦部長です)。" << std::endl;
}
// 要素の削除
std::cout << std::endl;
pilotUnit.erase("渚カヲル"); // カヲルを削除します
std::cout << "渚カヲルを削除しました。残り要素数: " << pilotUnit.size() << std::endl;
// ========================================
// 2. std::unordered_map の基本操作(ハッシュベースで高速です)
// ========================================
std::cout << std::endl;
std::cout << "=== std::unordered_map:パイロットのシンクロ率 ===" << std::endl;
std::unordered_map<std::string, double> syncRate;
// 要素の挿入(map と同じ方法で挿入できます)
syncRate["碇シンジ"] = 41.3;
syncRate["綾波レイ"] = 0.0; // 起動テスト後に上昇します
syncRate["惣流・アスカ・ラングレー"] = 67.0;
syncRate["渚カヲル"] = 99.9; // 第17使徒です
// 要素の更新(同じキーへの代入で上書きされます)
syncRate["綾波レイ"] = 83.0; // 弐号機での緊急テスト後です
syncRate["碇シンジ"] = 400.0; // 覚醒状態です
// 全要素の表示(順序は保証されません)
std::cout << "--- シンクロ率一覧(順不同)---" << std::endl;
for (auto it = syncRate.begin(); it != syncRate.end(); ++it) {
std::cout << " " << it->first << " : " << it->second << "%" << std::endl;
}
// find を使った安全なアクセス
std::cout << std::endl;
auto sit = syncRate.find("渚カヲル");
if (sit != syncRate.end()) {
std::cout << "渚カヲルのシンクロ率: " << sit->second << "%" << std::endl;
}
// ========================================
// 3. map を値として入れ子にする例(ネスト構造)
// ========================================
std::cout << std::endl;
std::cout << "=== ネストした map:パイロットのステータス ===" << std::endl;
// string → (string → int) のネストした map を使います
std::map<std::string, std::map<std::string, int>> pilotStatus;
pilotStatus["碇シンジ"]["メンタル"] = 40;
pilotStatus["碇シンジ"]["体力"] = 75;
pilotStatus["惣流・アスカ・ラングレー"]["メンタル"] = 80;
pilotStatus["惣流・アスカ・ラングレー"]["体力"] = 90;
pilotStatus["綾波レイ"]["メンタル"] = 55;
pilotStatus["綾波レイ"]["体力"] = 60;
for (auto it = pilotStatus.begin(); it != pilotStatus.end(); ++it) {
std::cout << it->first << " : ";
for (auto jt = it->second.begin(); jt != it->second.end(); ++jt) {
std::cout << jt->first << "=" << jt->second << " ";
}
std::cout << std::endl;
}
return 0;
}
# コンパイルします g++ -std=c++11 eva_map.cpp -o eva_map && ./eva_map === std::map:パイロットの搭乗機番号 === --- 全パイロット一覧(ソート済み)--- 惣流・アスカ・ラングレー : 第2号機 渚カヲル : 第2号機 碇シンジ : 第1号機 綾波レイ : 第0号機 --- 碇シンジの搭乗機: 第1号機 綾波レイの搭乗機: 第0号機(零号機) 惣流・アスカ・ラングレー は第2号機のパイロットです。 葛城ミサトはパイロットではありません(作戦部長です)。 渚カヲルを削除しました。残り要素数: 3 === std::unordered_map:パイロットのシンクロ率 === --- シンクロ率一覧(順不同)--- 渚カヲル : 99.9% 綾波レイ : 83% 碇シンジ : 400% 惣流・アスカ・ラングレー : 67% 渚カヲルのシンクロ率: 99.9% === ネストした map:パイロットのステータス === 惣流・アスカ・ラングレー : メンタル=80 体力=90 碇シンジ : メンタル=40 体力=75 綾波レイ : メンタル=55 体力=60
よくあるミス: operator[] で意図しない要素が作られる
operator[] は指定したキーが存在しない場合、値型のデフォルト値(数値なら 0、文字列なら空文字列)で自動的に要素を挿入します。「キーが存在するか確認する」つもりで operator[] を使うと、意図しない要素が増えてしまいます。存在確認には count() か find() を使います。
std::map<std::string, int> pilotUnit;
pilotUnit["碇シンジ"] = 1;
pilotUnit["綾波レイ"] = 0;
// NG: 存在確認のつもりが、新しい要素を作ってしまいます
if (pilotUnit["葛城ミサト"] == 0) {
// 「ミサトはいない」ではなく「ミサトをデフォルト値 0 で挿入した」になります
std::cout << "要素数: " << pilotUnit.size() << std::endl; // 3 になります
}
// OK: count() または find() で存在確認します
if (pilotUnit.count("葛城ミサト") == 0) {
std::cout << "葛城ミサトはパイロットではありません。" << std::endl;
std::cout << "要素数: " << pilotUnit.size() << std::endl; // 2 のままです
}
よくあるミス: unordered_map のハッシュ衝突で性能が劣化する
std::unordered_map は平均 O(1) のアクセスを実現しますが、ハッシュが衝突すると同じバケットに複数の要素が連なり、最悪 O(n) に劣化します。特にカスタム型をキーに使う場合やハッシュ関数の品質が低い場合に起こりやすいため、負荷係数(load factor)の監視やカスタムハッシュの適用を検討します。
std::unordered_map<std::string, double> syncRate; syncRate["碇シンジ"] = 400.0; syncRate["綾波レイ"] = 83.0; syncRate["惣流アスカ"] = 67.0; // 負荷係数(挿入済み要素数 / バケット数)を確認できます // 1.0 を超えるとハッシュ衝突が急増するため、rehash で拡張します std::cout << "load_factor: " << syncRate.load_factor() << std::endl; std::cout << "bucket_count: " << syncRate.bucket_count() << std::endl;
よくあるミス: カスタム型のキーに比較演算子が必要
std::map のキーには operator<(小なり演算子)が必要です。カスタム構造体をキーに使う場合は必ず実装します。std::unordered_map の場合は std::hash の特殊化と operator== が必要です。
// NG: operator< がない構造体をそのまま map のキーにはできません
struct PilotKey {
std::string name;
int unitNo;
};
// std::map<PilotKey, int> m; // コンパイルエラーになります
// OK: operator< を実装するとキーに使えます
struct PilotKey {
std::string name;
int unitNo;
bool operator<(const PilotKey& other) const {
if (name != other.name) return name < other.name;
return unitNo < other.unitNo;
}
};
std::map<PilotKey, double> syncMap;
syncMap[{"碇シンジ", 1}] = 400.0;
syncMap[{"綾波レイ", 0}] = 83.0;
syncMap[{"惣流アスカ", 2}] = 67.0;
for (auto it = syncMap.begin(); it != syncMap.end(); ++it) {
std::cout << it->first.name << "(第" << it->first.unitNo
<< "号機): " << it->second << "%" << std::endl;
}
概要
std::map はキーを赤黒木(Red-Black Tree)で管理するため、常にキーがソートされた状態に保たれます。検索・挿入・削除の計算量はいずれも O(log n) で、キーの順序が重要な場合や範囲検索が必要な場合に適しています。一方、std::unordered_map はハッシュテーブルを使うため、平均計算量は O(1) と高速ですが、キーの順序は保証されません。要素数が多く高速なアクセスが求められる場面で有利です。どちらも operator[] で存在しないキーを指定すると自動的に要素が作成されるため、読み取り専用のアクセスには find() や at() を使うことが多いです。at() はキーが存在しない場合に std::out_of_range 例外を投げるため、安全にアクセスできます。ネストした map を使えば多次元のキー構造も表現でき、設定データや統計情報の管理に役立ちます。
記事の間違いや著作権の侵害等ございましたらお手数ですがこちらまでご連絡頂ければ幸いです。