Vsvsvsvsvsvsvsvsvsvsvsvsvsvsvsvsvsvsvsvsvsvsvsvsvsvsvsvsvsvsvsvsvsvsvsv

我知道 at()[]慢,因为它的边界检查,这也在类似的问题如 C + + 矢量/[]运算符速度: : std: : Vector: : at () vs 操作符[] < < 惊人的结果! ! 慢5到10倍/更快!中讨论。我只是不明白 at()方法有什么好处。

如果我有一个像这样的简单向量: std::vector<int> v(10);,当我有一个 i索引并且我不确定它的向量边界时,我决定使用 at()而不是 []来访问它的元素,它迫使我使用 用 try-catch 块包起来:

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

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

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

所以我的问题是:
使用 矢量: : at比使用 操作符[]有什么优点?
什么时候应该使用 矢量: : at而不是 矢量: : 大小 + 操作符[]

79829 次浏览

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

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

我同意大多数对向量的出界访问是程序员的错误(在这种情况下,您应该使用 assert来更容易地定位这些错误; 大多数标准库的调试版本都会自动为您定位)。您不希望使用可以在上游吞噬的异常来报告程序员错误: 您希望能够使用 修好漏洞

由于对向量的出界访问不太可能是正常程序流的一部分(在这种情况下,你是对的: 预先检查 size,而不是让异常冒泡) ,我同意你的诊断: at基本上是无用的。

我要说的是,vector::at()抛出的异常实际上并不打算被紧接着的代码捕获。它们主要用于捕获代码中的 bug。如果需要在运行时进行边界检查,因为例如,索引来自用户输入,那么最好使用 if语句。所以总的来说,设计代码的意图是 vector::at()永远不会抛出异常,因此如果抛出异常,程序就会中止,这是 bug 的迹象。(就像 assert())

使用 Vector: : at over Vector: : 操作符[]的优点是什么? 什么时候应该使用 Vector: : at 而不是 Vector: : size + Vector: : 操作符[] ?

这里的重点是异常允许将正常的代码流从错误处理逻辑中分离出来,并且单个 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

在这种情况下,很难检查 不恰当地是否已经到达输入的末尾,因为这非常依赖于所遇到的确切标记。在每个使用点进行显式检查是痛苦的,而且随着前/后增量、使用点的偏移量、关于一些早期测试的持续有效性的错误推理等等的产生,程序员出错的空间要大得多。

使用异常的全部意义在于,您的错误处理代码可能更远。

在这种情况下,用户输入确实是一个很好的例子。假设您想要从语义上分析一个 XML 数据结构,该数据结构使用索引引用内部存储在 std::vector中的某种资源。现在 XML 树是一棵树,所以您可能想使用递归来分析它。在递归的内部,XML 文件的写入者可能存在访问冲突。在这种情况下,您通常希望跳出所有递归级别,只是拒绝整个文件(或任何类型的“粗糙”结构)。这时候就派上用场了。您可以只编写分析代码-如果文件是有效的。库代码将负责错误检测,您只需在粗级别捕获错误即可。

另外,像 std::map这样的其他容器也有 std::map::at,它的语义与 std::map::operator[]略有不同: at 可以在 const 映射上使用,而 operator[]不能。现在,如果您想编写容器不可知代码,比如可以处理 const std::vector<T>&const std::map<std::size_t, T>&的代码,那么 ContainerType::at将是您的首选武器。

然而,所有这些情况通常出现在处理某种未经验证的数据输入时。如果您确定您的有效范围(通常应该确定) ,通常可以使用 operator[],但更好的是,使用 begin()end()迭代器。

在调试构建中,不能保证 at()operator[]慢; 我希望它们的速度大致相同。不同之处在于,at()精确地指定了将会发生的情况,这里有一个边界错误(一个异常) , 与 operator[]的情况一样,这种未定义行为在我使用的所有系统(g + + 和 VC + +)中都是一种崩溃,至少在使用正常的调试标志时是如此。(另一个不同之处是,一旦我确定了我的代码,我就可以通过关闭调试来大幅度提高 operator[]的速度。如果表演需要的话ーー如果没有必要的话,我是不会这么做的。)

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

根据 这个的文章,撇开性能不谈,使用 atoperator[]没有任何区别,只有当访问被保证在向量的大小之内。否则,如果访问仅基于向量的容量,那么使用 at更安全。

如果有一个指向向量的指针,at会更清晰:

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

撇开性能不谈,第一个是更简单、更清晰的代码。

注意: 看起来有些新人对这个答案投了反对票,却没有礼貌地告诉他们哪里出了问题。下面的答案是正确的,可以验证 给你

实际上只有一个区别: at做边界检查,而 operator[]不做。这适用于调试构建和发布构建,这在标准中有很好的规定。就这么简单。

这使得 at成为一种较慢的方法,但是不使用 at也是非常糟糕的建议。你必须看绝对数字,而不是相对数字。我敢打赌,您的大部分代码执行的操作比 at更为复杂,代价也更高。就我个人而言,我尝试使用 at,因为我不希望一个讨厌的错误创建未定义行为和溜进生产。