vector :: at vs. vector :: operator []

Lih*_*ihO 79 c++ stl stdvector

我知道这at()[]它的边界检查慢,这也在类似的问题中讨论,如C++ Vector at/[] operator speed:: std :: vector :: at()vs operator [] << Surprising results !! 速度慢5到10倍!.我只是不明白这种at()方法有什么用处.

如果我有一个像这样的简单向量:std::vector<int> v(10);我决定通过使用at()而不是[]在我有索引的情况下访问它的元素i并且我不确定它是否在向量范围内,它迫使我用try-catch包装它块:

try
{
    v.at(i) = 2;
}
catch (std::out_of_range& oor)
{
    ...
}
Run Code Online (Sandbox Code Playgroud)

虽然我能够通过自己使用size()和检查索引来获得相同的行为,这对我来说似乎更容易和方便:

if (i < v.size())
    v[i] = 2;
Run Code Online (Sandbox Code Playgroud)

所以我的问题是:
使用vector :: at over vector :: operator []有什么好处?
我什么时候应该使用vector :: at而不是vector :: size + vector :: operator []

pmd*_*mdj 63

我会说vector::at()抛出的异常并不是真正意图被周围的代码捕获.它们主要用于捕获代码中的错误.如果您需要在运行时进行边界检查,因为例如索引来自用户输入,那么您最好使用if语句.总而言之,设计你的代码的目的是vector::at()永远不会抛出异常,这样如果确实如此,你的程序就会中止,这就是bug的标志.(就像一个assert())

  • @LihO:如果你的实现提供了`vector`的调试实现,那么最好将它用作"以防万一"选项而不是"at()".这样你就可以在发布模式下获得更多性能,以防万一你需要它. (8认同)
  • 是的,这些天大多数STL实现都支持一种调试模式,它甚至可以检查`operator []`,例如http://gcc.gnu.org/onlinedocs/libstdc++/manual/bk01pt03ch17s03.html#debug_mode.using.mode所以如果你的平台支持这个,你可能最好用它! (3认同)

Ale*_* C. 16

它迫使我用try-catch块包装它

不,它没有(try/catch块可以在上游).当您希望抛出异常而不是程序进入未定义的行为领域时,它非常有用.

我同意大多数越界访问向量是程序员的错误(在这种情况下,你应该使用assert更容易找到这些错误;标准库的大多数调试版本会自动为你执行此操作).您不希望使用可以向上游吞下的异常来报告程序员错误:您希望能够修复该错误.

因为对于向量的越界访问不太可能是正常程序流的一部分(在它是的情况下,你是对的:事先检查size而不是让异常冒泡),我同意你的诊断:at基本没用.

  • 如果不出意外的话,`at`在某种程度上是有用的,否则你会发现自己写的东西就像`if(i <v.size()){v [i] = 2; } else {throw what_are_you_doing_you_muppet(); }`.人们经常会想到"诅咒,我必须处理异常"的异常抛出函数,但只要你仔细记录每个函数可以抛出的内容,它们也可以用作"伟大的,我不会必须检查一个条件并抛出异常". (12认同)
  • @AlexandreC.官方的回应是`out_of_range`来自`logic_error`,其他程序员"应该"知道比抓住`logic_error上游并忽略它们更好.如果你的同事不想知道他们的错误,那么`assert`也可以被忽略,因为他们必须用`NDEBUG`编译你的代码会更难;每个机制都有它的优点和缺点. (6认同)

Ton*_*roy 11

使用vector :: at over vector :: operator []有什么好处?我什么时候应该使用vector :: at而不是vector :: size + vector :: operator []?

这里重要的一点是异常允许将正常的代码流与错误处理逻辑分离,并且单个catch块可以处理从无数抛出站点生成的问题,即使它们分散在函数调用的深处.因此,at()对于单次使用而言,这并不一定更容易,但是当您需要大量索引进行验证时,有时它会变得更容易 - 并且不会混淆正常情况逻辑.

值得注意的是,在某些类型的代码中,索引以复杂的方式递增,并且不断用于查找数组.在这种情况下,确保使用正确的检查要容易得多at().

作为一个真实世界的例子,我有代码将C++标记为词法元素,然后是将标记移动到标记向量的其他代码.根据遇到的情况,我可能希望增加并检查下一个元素,如:

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

在这种情况下,很难检查您是否已经不恰当地到达输入的末尾,因为这非常依赖于遇到的确切令牌.在每个使用点进行明确检查是痛苦的,并且程序员错误的余地更多,因为前/后增量,使用点的偏移,关于某些早期测试等的持续有效性的错误推理.


Bra*_*don 7

at 如果您有指向向量的指针,则可以更清楚:

return pVector->at(n);
return (*pVector)[n];
return pVector->operator[](n);
Run Code Online (Sandbox Code Playgroud)

除了性能,第一个是更简单明了的代码。


Jam*_*nze 5

首先,是否at()operator[]没有指定慢的.当没有边界错误时,我希望它们的速度大致相同,至少在调试版本中是这样.区别在于at()确切地指出了在边界错误(异常)中会发生什么,在这种情况下operator[],它是未定义的行为 - 在我使用的所有系统(g ++和VC++)中崩溃,至少当使用正常的调试标志.(另一个不同之处在于,一旦我确定了我的代码,我可以operator[] 通过关闭调试来大幅提高速度.如果性能需要它 - 除非必要,否则我不会这样做.)

在实践中,at()很少适合.如果上下文是这样的,你知道索引可能是无效的,你可能想要显式测试(例如,返回默认值或其他东西),如果你知道它不能无效,你想要中止(如果你不知道它是否可以无效,我建议你更精确地指定你的函数的界面.但是,有一些例外,其中无效索引可能是由解析用户数据引起的,而错误应该导致整个请求的中止(但不会导致服务器关闭); 在这种情况下,例外情况是适当的,并且at()会为您做到这一点.

  • 当`operator []`没有被强制进行边界检查时,为什么你会期望它们的速度大致相同,而`at()`是?您是否意味着缓存,推测和分支缓冲问题? (3认同)
  • 我更喜欢说“代码作为标准认可(保证)”的阵营;当然你可以在调试模式下自由交付,但是在进行跨平台开发时(包括但不限于相同操作系统,但不同编译器版本的情况),依赖标准是发布的最佳选择,而调试模式被认为是程序员获得大部分正确和健壮的工具:) (3认同)
  • 对不起,错过了你的"调试模式" - 属性.但是,我不会在调试模式下测量其质量的代码.在发布模式下,只需要`at()`进行检查. (2认同)