我知道 at()
比 []
慢是因为它的边界检查,这也在 C++ Vector at/[] operator speed 或 ::std::vector::at() vs operator[] << surprising results!! 5 to 10 times slower/faster! 等类似问题中进行了讨论。我只是不明白 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;
所以我的问题是:
使用 vector::at 比 vector::operator[] 有什么优势?
我什么时候应该使用 vector::at 而不是 vector::size + vector::operator[] ?
if (i < v.size()) v[i] = 2;
中,有一个可能的代码路径根本没有将 2
分配给 v
的任何元素。如果这是正确的行为,那就太好了。但是,当 i >= v.size()
时,此函数通常无法执行任何操作。因此,它不应该使用异常来指示意外情况并没有什么特别的原因。许多函数只使用 operator[]
而不检查大小,记录 i
必须在范围内,并将生成的 UB 归咎于调用者。
at
更安全。例如,给定一个包含 100 个元素的 obj
向量。 obj.at(143) = 69;
立即爆炸。然而,obj[143] = 69;
会在您不通知的情况下悄悄潜入。
我想说 vector::at()
抛出的异常并不是真正打算被周围的代码捕获。它们主要用于捕获代码中的错误。如果您需要在运行时进行边界检查,因为例如索引来自用户输入,那么您确实最好使用 if
语句。因此,总而言之,设计您的代码的目的是让 vector::at()
永远不会抛出异常,这样如果发生异常并且您的程序中止,则表明存在错误。 (就像 assert()
)
它迫使我用 try-catch 块包装它
不,它没有(try/catch 块可以在上游)。当您希望抛出异常而不是您的程序进入未定义的行为领域时,它很有用。
我同意大多数对向量的越界访问是程序员的错误(在这种情况下,您应该使用 assert
更容易地定位这些错误;大多数调试版本的标准库会自动为您执行此操作)。您不想使用可以被上游吞下的异常来报告程序员的错误:您希望能够修复错误。
由于对向量的越界访问不太可能是正常程序流程的一部分(在这种情况下,您是对的:事先使用 size
检查而不是让异常冒泡),我同意您的诊断:at
基本上没用。
out_of_range
异常,则调用 abort()
。
try..catch
可以出现在调用此方法的方法中。
at
很有用,否则您会发现自己在编写类似 if (i < v.size()) { v[i] = 2; } else { throw what_are_you_doing_you_muppet(); }
的内容。人们通常会以“诅咒,我必须处理异常”来考虑抛出异常的函数,但只要你仔细记录你的每个函数可以抛出什么,它们也可以用作“太好了,我不必须检查条件并抛出异常”。
out_of_range
派生自 logic_error
,其他程序员“应该”知道最好不要在上游捕获 logic_error
并忽略它们。如果您的同事不想知道他们的错误,assert
也可以忽略,因为他们必须使用 NDEBUG
编译您的代码;-) 每种机制都有其优点和缺陷。
与 vector::operator[] 相比,使用 vector::at 有什么优势?我什么时候应该使用 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
在这种情况下,很难检查您是否不恰当地到达了输入的末尾,因为这非常依赖于遇到的确切标记。在每个使用点进行显式检查是很痛苦的,并且由于前/后增量、使用点的偏移量、关于某些早期测试的持续有效性的有缺陷的推理等开始出现,程序员错误的空间要大得多。
如果您有指向向量的指针,at
会更清楚:
return pVector->at(n);
return (*pVector)[n];
return pVector->operator[](n);
抛开性能不谈,第一个是更简单、更清晰的代码。
at()
的理由。只需编写:auto& vector = *pVector;
,现在您可以执行 return vector[n]
。此外,您真的应该避免直接使用指针(而不是引用),尤其是对于复杂的类。
at()
...简单地用 operator[]
替换所有调用使代码运行得足够快以通过所有测试。 at()
和 operator[]
具有非常明显的性能差异。
在调试版本中,不能保证 at()
比 operator[]
慢;我希望它们的速度大致相同。不同之处在于 at()
准确地指定了在出现边界错误(异常)时会发生什么,而在 operator[]
的情况下,它是未定义的行为——在我使用的所有系统(g++ 和VC++),至少在使用正常调试标志时。 (另一个区别是,一旦我确定我的代码,我可以通过关闭调试来显着提高 operator[]
的速度。如果性能需要它 - 除非有必要,否则我不会这样做。)
实际上,at()
很少适用。 如果上下文是您知道索引可能无效的情况,您可能需要显式测试(例如返回默认值或其他内容),并且如果您知道它不能无效,您想中止(如果您不知道它是否无效,我建议您更精确地指定函数的接口)。但是,有一些例外情况,无效索引可能是由于解析用户数据而导致的,并且该错误应该导致整个请求中止(但不会导致服务器停机);在这种情况下,例外是合适的,at()
会为您完成。
operator[]
不需要进行边界检查,但所有好的实现都可以。至少在调试模式下。唯一的区别是如果索引超出范围,它们会做什么:operator[]
中止并显示错误消息,at()
引发异常。
std::string
并不总是有效:-MD
,您最好关闭检查,{ 3},你最好戴上它。)
operator[]
的每一次访问都应该被削弱吗?例如,std::vector<color> surface(witdh*height); ...; for (int y=0; y!=height; ++y)...
。我认为对交付的二进制文件执行边界检查属于过早悲观。恕我直言,它应该只是对设计不佳的代码的创可贴。
使用异常的全部意义在于您的错误处理代码可以离得更远。
在这种特定情况下,用户输入确实是一个很好的例子。假设您想从语义上分析一个 XML 数据结构,该结构使用索引来引用您内部存储在 std::vector
中的某种资源。现在 XML 树是一棵树,所以您可能想使用递归来分析它。在递归的深处,XML 文件的编写者可能存在访问冲突。在这种情况下,您通常希望退出所有递归级别并拒绝整个文件(或任何类型的“粗略”结构)。这就是 at 派上用场的地方。您可以编写分析代码,就好像文件是有效的一样。库代码将负责错误检测,您可以在粗略的级别上捕获错误。
此外,其他容器(如 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()
的迭代器。
根据 this 文章,抛开性能不谈,使用 at
或 operator[]
没有任何区别,只要保证访问在向量的大小范围内。否则,如果访问仅基于向量的容量,则使用 at
更安全。
注意: 似乎有些新人在没有礼貌地告诉问题的情况下对这个答案投了反对票。以下答案是正确的,可以验证here。
实际上只有一个区别:at
进行边界检查,而 operator[]
没有。这适用于调试版本和发布版本,标准对此进行了很好的规定。就是这么简单。
这使 at
成为一种较慢的方法,但不使用 at
也是非常糟糕的建议。您必须查看绝对数字,而不是相对数字。我可以肯定地打赌,您的大部分代码都在执行比 at
更昂贵的操作。就个人而言,我尝试使用 at
,因为我不希望一个讨厌的错误创建未定义的行为并潜入生产环境。
std::out_of_range
或任何形式的 std::logic_error
实际上本身就是一个逻辑错误 here。
at
和 []
之间的区别,我的回答只是说明了区别。当性能不是问题时,我个人使用“安全”方法。正如 Knuth 所说,不要过早优化。此外,无论哲学上的差异如何,在生产中尽早发现错误是件好事。
at
,只要它不在代码中对性能非常敏感的部分。立即抛出异常要好得多,而不是程序继续处理虚假数据,这可能会导致比不明显的性能差异更严重的问题。
size()
+[]
,在索引永远不会越界的情况下使用assert
以便将来轻松修复错误,在所有其他情况下使用.at()
(以防万一,导致可能发生错误......)vector
的调试实现,那么最好将其用作“以防万一”选项,而不是到处使用at()
。这样,您就可以希望在发布模式下获得更高的性能,以防万一您需要它。operator[]
,例如 gcc.gnu.org/onlinedocs/libstdc++/manual/…,所以如果您的平台支持这一点,您可能最好使用它!