0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

[C++] [i] と at(i) の違い

Posted at

In a nutshell

C++における配列的な要素アクセスには,[i](添字演算子)と at(i)(メンバ関数)の2つがある.どちらも似たように使えるが,安全性・エラーハンドリング・パフォーマンスの面で大きく異なる.両方の選択肢が利用できるのであれば, at(i) を利用する方が安全である.

はじめに

C++ には,std::vectorstd::string などのコンテナから要素を取得する方法として [i]at(i) がある.普段は特に意識せず [i] を使用していたが,at(i) を使っているスクリプトを見た際に,この違いについて理解していない事に気が付いた.そこで,両者の違いについて,以下に纏めた.

予想される読み手

  • C++に関して初学者である

基本的な仕様の違い(表と解説)

vec[i] は配列のように要素へ直接アクセスでき,パフォーマンスも高いが,インデックスが有効かどうかのチェックが行われないため,無効なインデックスを指定した場合,未定義動作(クラッシュや意図しない動作)となる.一方で vec.at(i) は,アクセス前にインデックスの範囲チェックを自動で行い,無効な場合は std::out_of_range 例外を投げる.そのため,安全に利用できるが,若干パフォーマンスに劣る.

機能 [i] at(i)
範囲チェック なし(未定義動作) あり(範囲外で例外を投げる)
戻り値型 要素の参照(読み書き可能) 要素の参照(読み書き可能)
例外発生 なし 範囲外で std::out_of_range
パフォーマンス 高い やや低い(範囲チェックあり)
noexcept yes no(例外を投げる可能性あり)

具体的な違いとコード例

[i] の使用例

範囲外にアクセスしても,エラーを吐かずにバグを引き起こすことがある.したがって,検知されにくく,大変に厄介で危険である.

#include <vector>
#include <iostream>

int main() {
    std::vector<int> vec = {10, 20, 30};
    std::cout << vec[1] << std::endl; // 出力: 20
    std::cout << vec[3] << std::endl; // 範囲外 -> 未定義動作
}

at(i) の使用例

at() を使うことで,安全にエラー検知が可能.バグ等の回避に貢献する.

#include <vector>
#include <iostream>

int main() {
    std::vector<int> vec = {10, 20, 30};
    try {
        std::cout << vec.at(1) << std::endl; // 出力: 20
        std::cout << vec.at(3) << std::endl; // 例外が発生
    } catch (std::out_of_range& e) {
        std::cerr << "範囲外アクセス: " << e.what() << std::endl;
    }
}

使い分け

at(i) を使うべき場面

  • 安全性を重視する
  • デバッグ中
  • ユーザー入力など,外部からのインデックス指定がある
  • 本番環境で不正アクセスを,明示的に防ぎたい

[i] を使うべき場面

  • パフォーマンスが極めて重要なケース
  • アクセスするインデックスが常に正しいと保証されている
  • 大量の要素に対してループ処理をする(速度優先)

注意点・例外的なこと

その他のコンテナとの関係

  • std::array, std::stringat() を持つ.
  • std::map, std::unordered_mapoperator[] は,存在しないキーにアクセスすると新しい要素が追加される.
  • at() は同様の状況で例外を投げるため,意図しない挿入を防げる.
#include <map>
#include <iostream>

int main() {
    std::map<std::string, int> mp;
    mp["key1"] = 10;
    std::cout << mp["key2"] << std::endl; // 0が挿入される

    try {
        std::cout << mp.at("key3") << std::endl; // 例外発生
    } catch (std::out_of_range& e) {
        std::cout << "存在しないキー: " << e.what() << std::endl;
    }
}

特殊ケース・補足事項

const に対する動作

読み取り専用でも [i], at(i) の両方は使えるが,書き込みはできない.

const std::vector<int> v = {1, 2, 3};
int x = v[1];      // 読み取りのみ可能
int y = v.at(2);   // 読み取りのみ可能

std::vector<bool> の特殊性

std::vector<bool> は特殊化されており,[i]at(i) の戻り値は bool& ではなくプロキシオブジェクトになる.このため,ポインタ取得や参照渡しなど一部の操作が制限される.

std::vector<bool> flags = {true, false};
auto val = flags[0];  // 実際には bool ではない

多次元配列へのアクセス

ネストされたコンテナでは,at() を二重に使うことで,階層ごとに安全なアクセスが可能.

std::vector<std::vector<int>> matrix = {{1, 2}, {3, 4}};
int a = matrix[1][0];             // 危険(未定義動作の可能性)
int b = matrix.at(1).at(0);       // 安全なアクセス

例外処理の有無による関数設計の違い

  • at()noexcept ではないため,例外を許容しない関数では使用に注意.
  • noexcept 指定された関数内では使用できないケースもある.

C++20以降の std::span の活用

  • std::span は軽量なビュー型で,安全性と柔軟性を兼ね備える.
  • インデックスアクセスに at() のような安全機構を追加できる設計も可能.

まとめ

観点 [i] at(i)
安全性 低い(未定義動作のリスク) 高い(例外で保護される)
パフォーマンス 高い(範囲チェックなし) やや低い(範囲チェックあり)
デバッグ適性 低い(バグに気づきにくい) 高い(例外で検出しやすい)
推奨場面 高速処理・内部処理 ユーザー入力・教育・バグ予防
noexcept yes no(例外を投げる可能性がある)
対応コンテナ ほとんどのコンテナ 一部コンテナ(例:list にはなし)

Summary

  • at(i) は 安全性が高く例外を投げる.
  • [i] は 高速だが,範囲外アクセスは未定義動作.
  • 初心者や安全重視の開発では at() を使うのが推奨される.
  • パフォーマンスを優先する場面では [i] も選択肢になるが,慎重な設計が求められる.

References

cppreference.com - C++ > Containers library > std::vector
cppreference.com - C++ > Containers library >std::map

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?