vector :: atとvector :: operator []


95

私はそれat()[]その境界チェックのために遅いことを知っています、それは同様にC ++ Vector at / []オペレーター速度 または:: std :: vector :: at()対operator []のような同様の質問で議論されます<<驚くべき結果!! 5〜10倍遅い/速い!。私at()はその方法が何に適しているのか理解できません。

このような単純なベクトルがある場合:インデックスがあり、ベクトルが境界にあるかどうかわからない状況ではなく、std::vector<int> v(10);を使用してその要素にアクセスすることにした場合、try-catchで強制的にラップするブロックat()[]i

try
{
    v.at(i) = 2;
}
catch (std::out_of_range& oor)
{
    ...
}

私はsize()自分でインデックスを使用して確認することで同じ動作を得ることができますが、これは私にとってより簡単で非常に便利です:

if (i < v.size())
    v[i] = 2;

だから私の質問は:vector :: operator []よりvector :: at
を使用する利点は何ですか?
いつvector :: size + vector :: operator []ではなくvector :: atを使用すればよいですか?


11
+1非常に良い質問!! しかし、私はat()が一般的に使用されているとは思いません。
Rohit Vipin Mathews、2012

10
例のコードではif (i < v.size()) v[i] = 2;、の2要素にまったく割り当てられていない可能性のあるコードパスがあることに注意してくださいv。それが正しい動作であれば、すばらしいです。しかし、多くの場合、この関数がにできることは賢明なものではありませんi >= v.size()。したがって、予期しない状況を示すために例外を使用してはならない特別な理由はありません。多くの関数operator[]は、サイズをチェックせずに使用しi、範囲内にある必要のあるドキュメントを作成し、結果のUBを呼び出し元に通知します。
スティーブジェソップ

回答:


74

vector::at()スローする例外は、すぐ近くにあるコードによってキャッチされることを実際には意図していないと思います。これらは主に、コード内のバグを見つけるのに役立ちます。たとえば、インデックスがユーザー入力から取得されるため、実行時に境界チェックが必要な場合は、ifステートメントを使用するのが最善です。したがって、要約するvector::at()と、例外をスローしないことを意図してコードを設計します。そうすると、例外がスローされてプログラムが異常終了する場合、それはバグの兆候です。(のようにassert()


1
+1間違ったユーザー入力の処理を分離する方法の説明が好き(入力の検証。無効な入力が予期される場合があるため、例外的なものとは見なされません)...そしてコード内のバグ(範囲外の反復子の参照は例外的です)事)
Bojan Komazec

つまり、インデックスがユーザーの入力に依存する場合はsize()+ を使用する必要があると言い、将来の簡単なバグ修正のためにインデックスが範囲外になってはならない状況や、他のすべての状況で使用します(万が一、何か問題が発生する可能性があるため)。 。)[]assert.at()
LihO

8
@LihO:実装がデバッグ実装を提供している場合はvector、それをat()すべての場所ではなく、「念のため」のオプションとして使用する方がおそらく良いでしょう。そうすれば、リリースモードで必要な場合に備えて、もう少しパフォーマンスを期待できます。
スティーブジェソップ

3
ええ、最近のほとんどのSTL実装はoperator[]gcc.gnu.org / onlinedocs / libstdc ++ / manual /…などの境界チェックを行うデバッグモードをサポートしているので、プラットフォームがこれをサポートしている場合は、おそらくそれを使用するのが最善です。
pmdj

1
@pmdjファンタスティックポイント、私が知らなかった...孤立したリンク。:P現在のものは:gcc.gnu.org/onlinedocs/libstdc++
underscore_d

16

強制的にtry-catchブロックでラップする

いいえ、ありません(try / catchブロックを上流にすることができます)。プログラムではなく例外をスローして、未定義の動作領域に入るようにする場合に役立ちます。

私は、ベクトルへの範囲外のアクセスのほとんどがプログラマーのミスであることにも同意します(その場合、assertそれらのミスをより簡単に見つけるために使用する必要があります。標準ライブラリのほとんどのデバッグバージョンがこれを自動的に行います)。プログラマのミスを報告するために上流で飲み込むことができる例外を使用したくない:バグ修正できるようにしたい。

ベクトルへの境界外のアクセスが通常のプログラムフローの一部である可能性は低いため(そうである場合は、あなたが正しい:size例外をバブルアップさせるのではなく、事前に確認してください)、診断に同意します。at本質的に役に立たない。


out_of_range例外をキャッチしない場合abort()は、呼び出されます。
LihO

@LihO:必ずしもそうでtry..catchはありません。
のNaveen

12
他に何もないat場合は、他の方法で自分がのようなものを書いていることに気付く範囲で役に立ちますif (i < v.size()) { v[i] = 2; } else { throw what_are_you_doing_you_muppet(); }。多くの場合、例外をスローする関数は「呪い、例外を処理する必要がある」と見なされますが、各関数がスローできるものを注意深く文書化する限り、それらは「素晴らしい、私はできません」としても使用できます条件をチェックして例外をスローする必要があります。」
スティーブジェソップ

@SteveJessop:他のプログラマによって上流でキャッチされる可能性があるため、プログラムのバグの例外をスローするのは好きではありません。ここではアサーションがはるかに役立ちます。
アレクサンドル

6
@AlexandreC。まあ、それに対する公式の返答out_of_rangeはから派生したものlogic_errorであり、他のプログラマはlogic_errorsを上流でキャッチして無視するよりも「知っているべき」です。assert同僚が間違いを知らないことに熱心である場合も無視できます。NDEBUG;-)でコードをコンパイルする必要があるため、それはさらに難しくなります。それぞれのメカニズムには長所と短所があります。
スティーブジェソップ

11

vector :: operator []よりvector :: atを使用する利点は何ですか?いつvector :: size + vector :: operator []ではなくvector :: atを使用すればよいですか?

ここで重要な点は、例外により、通常のコードフローをエラー処理ロジックから分離できることと、関数呼び出し内に分散していても、単一のcatchブロックが無数のスローサイトから生成された問題を処理できることです。そのat()ため、1回の使用では必ずしも簡単ではありませんが、検証するインデックスが大量にあると、通常のケースのロジックがわかりやすくなり、わかりやすくなります。

また、一部のタイプのコードでは、インデックスが複雑な方法で増分され、配列を検索するために継続的に使用されていることも注目に値します。そのような場合、を使用して正しいチェックを確実にする方がはるかに簡単at()です。

実際の例として、C ++を字句要素にトークン化するコードと、トークンのベクトル上にインデックスを移動する他のコードがあります。何に遭遇するかに応じて、次のように次の要素をインクリメントしてチェックしたい場合があります。

if (token.at(i) == Token::Keyword_Enum)
{
    ASSERT_EQ(tokens.at(++i), Token::Idn);
    if (tokens.at(++i) == Left_Brace)
        ...
    or whatever

この種の状況では、入力の最後に不適切に到達したかどうかを確認することは非常に困難です。これは、発生した正確なトークンに大きく依存しているためです。使用の各ポイントでの明示的なチェックは苦痛であり、事前/事後の増分、使用ポイントでのオフセット、以前のテストの継続的な有効性に関する欠陥のある推論などが原因で、プログラマーのエラーが発生する余地がはるかにあります。


10

at あなたがベクトルへのポインタを持っている場合、より明確にすることができます:

return pVector->at(n);
return (*pVector)[n];
return pVector->operator[](n);

パフォーマンスはさておき、これらの最初のものは、よりシンプルで明確なコードです。


...特に、ベクトルのn番目の要素へのポインタが必要な場合。
イルカ、2014年

4

まず、遅いかどうat()operator[]は指定されていません。境界エラーがない場合、少なくともデバッグビルドでは、それらがほぼ同じ速度であると期待します。違いはat()、境界エラー(例外)が発生した場合に何が起こるかを正確に指定することです。ここでは、の場合と同様にoperator[]、未定義の動作、つまり、使用するすべてのシステム(g ++およびVC ++)でのクラッシュ、少なくとも通常のデバッグフラグが使用されます。(もう1つの違いは、コードを確認したらoperator[] 、デバッグをオフにすることで速度が大幅に向上することです。パフォーマンスに必要な場合は、必要でない限り実行しません。)

実際にat()は、めったに適切ではありません。コンテキストが、インデックスが無効である可能性があることがわかっている場合は、おそらく明示的なテスト(たとえば、デフォルト値などを返す)が必要であり、無効にできないことがわかっている場合は、中止する必要があります(それが無効であるかどうかわからないので、関数のインターフェイスをより正確に指定することをお勧めします)。ただし、いくつかの例外はありますが、無効なインデックスがユーザーデータの解析に起因する場合があり、エラーによって要求全体が中止されます(ただし、サーバーはダウンしません)。そのような場合、例外が適切であり、それat()を行います。


4
operator[]が境界チェックを強制されないのに、なぜそれらがほぼ同じ速度であると期待するのat()ですか?キャッシュ、推測、および分岐バッファの問題を示唆しているのですか?
セバスチャンマッハ

@phresnel operator[]は境界チェックを行う必要はありませんが、優れた実装はすべて行う必要があります。少なくともデバッグモードでは。唯一の違いは、インデックスが範囲外の場合の動作です。operator[]エラーメッセージで中止されat()、例外がスローされます。
James Kanze

2
「デバッグモード」属性がありません。ただし、デバッグモードではコードの品質を測定しません。リリースモードでは、チェックはでのみ必要ですat()
セバスチャンマッハ

1
@phresnel私が提供したコードのほとんどは「デバッグ」モードになっています。パフォーマンスの問題で実際に必要な場合にのみ、チェックをオフにします。(ここでは、Microsoft 2010より前の問題が少し問題std::stringでした。チェックオプションがランタイムのオプションと対応していない場合、常に機能するとは限りませんでした。チェック-MDをオフにした-MDd方がよいでしょう。それをオンにします。)
James Kanze

2
私は、「標準で認可された(保証された)コード」と言っている陣営です。もちろん、デバッグモードで自由に提供できますが、クロスプラットフォーム開発(同じOSの場合、ただし異なるバージョンのコンパイラの場合を含む)を行う場合、リリースとデバッグモードには標準に依存することが最善の策ですプログラマーがそのことをほぼ正しくて堅牢にするためのツールと見なされます:)
Sebastian Mach

1

例外を使用する重要な点は、エラー処理コードがさらに離れている可能性があることです。

この特定のケースでは、ユーザー入力は確かに良い例です。XMLのデータ構造を意味的に分析して、インデックスを使用して内部でに格納しているある種のリソースを参照するとしstd::vectorます。これでXMLツリーはツリーになりました。おそらく、再帰を使用してそれを分析したいと思うでしょう。深いところにある再帰では、XMLファイルの作成者によるアクセス違反が発生する可能性があります。その場合、通常、再帰のすべてのレベルから出て、ファイル全体(またはあらゆる種類の「粗い」構造)を拒否するだけです。これが便利なところです。ファイルが有効であるかのように、分析コードを記述するだけです。ライブラリー・コードがエラー検出を処理し、大まかなレベルでエラーをキャッチできます。

また、他のコンテナは、のようにstd::map、また持っているstd::map::atよりもわずかに異なる意味を持っているstd::map::operator[]:一方で、constのマップに使用することができますではoperator[]できません。ここで、const std::vector<T>&またはのいずれかconst std::map<std::size_t, T>&に対処できるようなコンテナにとらわれないコードを記述しContainerType::atたい場合は、武器を選択します。

ただし、通常、これらのケースはすべて、ある種の未検証のデータ入力を処理するときに発生します。あなたの有効範囲について確信している場合は、通常あるべきように、あなたは、通常使用することができoperator[]ますが、いっそのこと、とイテレータbegin()end()


1

この記事によると、パフォーマンスはさておき、ator を使用しても違いはありません。operator[]アクセスがベクターのサイズ内であることが保証されている場合のみです。それ以外の場合、アクセスがベクトルの容量のみに基づいている場合は、使用する方が安全atです。


1
ドラゴンがいる。そのリンクをクリックするとどうなりますか?(ヒント:私はすでにそれを知っていますが、StackOverflowでは、リンクの腐敗のないコメント、つまり、あなたが言いたいことについての短い要約を提供するコメント)を好んでいます)
Sebastian Mach

先端をありがとう。現在は修正されています。
ahj

0

注:一部の新しい人々は、何が悪いのかを知らせる礼儀なく、この回答に反対票を投じているようです。以下の答えは正しく、ここで確認できます

実際には1つだけ違いがあります。at境界チェックをoperator[]行うのにそうではありません。これは、リリースビルドだけでなくデバッグビルドにも適用されます。これは、標準で非常に明確に指定されています。とても簡単です。

これによりatメソッドが遅くなりますが、を使用しないことも非常に悪いアドバイスatです。相対数ではなく、絶対数を調べる必要があります。あなたのコードのほとんどが、よりもはるかに高価な操作をしていることは間違いありませんat。個人的に、at未定義の動作を作成して本番環境に忍び込む厄介なバグが欲しくないので、使用を試みます。


1
C ++の例外は、エラー処理メカニズムであり、デバッグ用のツールではありません。ハーブサッターは、なぜ投げるのstd::out_of_rangeか、なんらかの形std::logic_errorが、実際には論理エラーである理由を説明しています
Big Temp

@BigTemp-あなたのコメントがこの質問と回答にどのように関連しているかはわかりません。はい、例外は非常に話題に議論されているが、ここでの質問は間の差であるat[]、私の答えは単に違いを述べています。個人的には、パフォーマンスに問題がない場合は「安全な」方法を使用します。Knuthが言うように、時期尚早の最適化は行わないでください。また、哲学的な違いに関係なく、本番よりも早い段階でバグを検出することは良いことです。
Shital Shah
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.