目次
はじめに
C++のstd::vector
やstd::map
といったコンテナに格納された要素にアクセスする際、よく似た2つの方法があります。
- 添字演算子
[]
:my_vector[5]
やmy_map["key"]
のように、角括弧でアクセスする方法。 .at()
メンバ関数:my_vector.at(5)
やmy_map.at("key")
のように、メンバ関数でアクセスする方法。
普段は何気なく []
を使っている方も多いかもしれませんが、この2つの方法には、指定した要素が存在しなかった場合の動作に、プログラムの安全性を左右する重要な違いがあります。
この記事では、.at()
と []
の違いと、それぞれの適切な使い分けについて解説します。
.at()
と []
のサンプルコード
このコードは、std::map
に対して、存在するキーと存在しないキーの両方で、.at()
と []
を使ってアクセスを試みます。
完成コード
#include <iostream>
#include <vector>
#include <string>
#include <map>
#include <stdexcept> // out_of_range
using namespace std;
int main() {
map<string, int> stock = {{"apple", 10}, {"orange", 18}};
// --- 1. .at(): 安全なアクセス(範囲外チェックあり) ---
cout << "--- .at()のテスト ---" << endl;
try {
// 存在するキーへのアクセス
cout << "appleの在庫: " << stock.at("apple") << endl;
// 存在しないキー("grape")へのアクセス
cout << "grapeの在庫: " << stock.at("grape") << endl; // この行で例外が発生
}
catch (const out_of_range& e) {
cout << "エラー: " << e.what() << endl;
}
cout << "\n--- 2. []: 高速なアクセス(範囲外チェックなし) ---" << endl;
// 存在するキーへのアクセス
cout << "orangeの在庫: " << stock["orange"] << endl;
// 存在しないキー("banana")へのアクセス
// -> mapに "banana":0 という新しい要素が自動で追加される!
cout << "bananaの在庫: " << stock["banana"] << endl;
cout << "\n--- 最終的なmapの状態 ---" << endl;
for (const auto& [key, value] : stock) {
cout << key << ": " << value << endl;
}
return 0;
}
コードの解説
1. .at()
メンバ関数
- 動作: 指定されたキー(またはインデックス)が存在するかを必ずチェックします。
- 存在しない場合:
std::out_of_range
という例外を投げます。プログラムはクラッシュしませんが、try-catch
ブロックでこの例外を適切に処理する必要があります。 - 使いどころ:
- アクセスしようとしているキーが存在するか不確実な場合。
- 範囲外アクセスを致命的なエラーとして、安全に処理したい場合。
2. 添字演算子 []
- 動作: 範囲外チェックを行いません。
- 存在しない場合 (
map
):std::map
の[]
演算子は、もし指定したキーが存在しなければ、そのキーと値初期化された値(int
なら0
)を持つ新しい要素を、その場に自動で追加してしまいます。 - 存在しない場合 (
vector
):std::vector
の[]
演算子は、もし範囲外のインデックスにアクセスすると、チェックを行わないため、未定義の動作(プログラムのクラッシュなど)を引き起こします。非常に危険です。 - 使いどころ:
- アクセスするキーが確実に存在すると分かっている場合。
.at()
よりもわずかに高速です。 map
で、キーが存在しなければ新しい要素を追加したい、という意図がある場合。
- アクセスするキーが確実に存在すると分かっている場合。
まとめ
.at() | [] | |
範囲外チェック | あり | なし |
存在しない要素へのアクセス | 例外 (out_of_range ) を投げる | map : 新要素を追加<br>vector : 未定義動作(危険!) |
パフォーマンス | わずかに遅い | わずかに速い |
推奨される使い方 | 安全な読み取り | 存在が確実な要素の読み書き<br>(map での要素追加) |
結論として、プログラムの安全性を最優先するならば、常に .at()
を使うのが良い習慣です。 []
演算子の挙動は便利ですが、特に vector
での範囲外アクセスは深刻なバグの原因となるため、そのリスクを十分に理解した上で使いましょう。