STL のvector
を使っていて頭に入れておくべきことがあったので、ここにメモしておきます。
std::vector
の n 番目の要素の参照を返す(要素へアクセスする)方法は大きく2つあります。
さあ、あなたはどっち?
※iterator
を使ったアクセスについてはまた改めて!
#vector::at
境界チェックをする(vector
の管理範囲外にアクセスしていないか)
範囲外アクセスをした時、std::out_of_range
の例外を送出する。
範囲外アクセスチェックのため、若干の OH(オーバーヘッド)が生じる。
// vectorにある要素の値を変数に代入したり
variable = vector.at(index);
// ある値をvectorにある要素に代入したり
vector.at(index) = value;
#vector::operator[]
境界チェックをしない
範囲外アクセスをした時、 未定義動作を行う。
範囲外アクセスチェックをしないため、OH が生じない。
// vectorにある要素の値を変数に代入したり
variable = vector[index];
// ある値をvectorにある要素に代入したり
vector[index] = value;
#サンプルコード
「え?どゆこと?」
そう思ったあなた、是非ご自身の環境で以下のコードを試してみて下さい。
あ、for 文のindexに注意して下さいね。
#include <iostream>
#include <vector>
#include <stdexcept>
int main()
{
std::vector<int> sampleVector;
// sampleVectorのindex0~9に要素を詰める.
for (int i = 0; i < 10; i++)
{
sampleVector.push_back(i);
}
std::cout << "###std::vector::at" << std::endl;
// at
try
{
// sampleVectorの管理範囲外であるindex10の要素にアクセスする.
for (int i = 1; i <= 10; i++)
{
std::cout << sampleVector.at(i) << std::endl;
}
// atの境界チェックで例外を投げるため、この処理は実行されない.
std::cout << "Complete..." << std::endl;
}
catch (std::out_of_range& sampleException)
{
std::cout << "out of range!" << std::endl;
std::cout << "Error : " << sampleException.what() << std::endl;
}
std::cout << std::endl << "###std::vector::operator[]" << std::endl;
// operator[]
try
{
// sampleVectorの管理範囲外であるindex10の要素にアクセスする.
for (int i = 1; i <= 10; i++)
{
std::cout << sampleVector[i] << std::endl;
}
// 境界チェック無しのため、そのまま不定値が出力され、実行完了となる.
std::cout << "Complete..." << std::endl;
}
// 境界チェック無しのため、例外は投げない.
catch (std::out_of_range& sampleException)
{
std::cout << "out of range!" << std::endl;
std::cout << "Error : " << sampleException.what() << std::endl;
}
std::cout << "End..." << std::endl;
return 0;
}
###std::vector::at
1
2
3
4
5
6
7
8
9
out of range!
Error : invalid vector subscript
###std::vector::operator[]
1
2
3
4
5
6
7
8
9
2011707104
Complete...
End...
おやおや、全然違う結果になっていますねぇ
へっへっへ・・・
std::vector::operator[]
の方は、なんだかとんでもない値を出力していますねぇ
へっへっへ・・・
#まとめ
上記2つの違いは「範囲外アクセスをした時に、未定義動作を行うか例外を投げるか」です。
防御的プログラミングということであれば、未定義動作を行わないことが保証されている方をできるだけ使うべきだと思います。
at
は範囲外アクセスに対してstd::out_of_range
例外を投げるため、適切に処理されなかった場合、std::terminate
が呼ばれてプログラムが異常終了します。
例外処理を用意していなかったとしても、プログラムが終了するだけです。
デバッグも比較的容易でしょう。
未定義動作を原因とするバグは、実際に未定義動作を行った箇所と異なる箇所で致命的なエラーを発生させることも多くあります。
エラー発生箇所と原因箇所が異なる場合、デバッグは困難になるでしょう。
個人的にはまずat
を使うことをオススメしたいかなぁ。
at
派 vs operator[]
派の議論を見たいので、よかったらコメント下さい!