[英]vector<vector<largeObject>> vs. vector<vector<largeObject>*> in c++
[英]vector::at vs. vector::operator[]
我知道at()
比[]
慢,因为它的边界检查,这也在类似的问题中讨论过,比如C++ Vector at/[] operator speed或::std::vector::at() vs operator[] < <令人惊讶的结果!! 慢/快 5 到 10 倍! . 我只是不明白at()
方法有什么用。
如果我有一个像这样的简单向量: std::vector<int> v(10);
我决定使用at()
而不是[]
来访问它的元素,当我有一个索引i
并且我不确定它是否在向量范围内时,它迫使我用 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[] ?
我想说vector::at()
抛出的异常并不是真的要被周围的代码捕获。 它们主要用于捕获代码中的错误。 如果您需要在运行时进行边界检查,因为例如索引来自用户输入,那么您确实最好使用if
语句。 因此,总而言之,设计您的代码时应确保vector::at()
永远不会抛出异常,因此如果抛出异常,并且您的程序中止,则表明存在错误。 (就像一个assert()
)
它迫使我用 try-catch 块包装它
不,它没有(try/catch 块可以在上游)。 当您希望抛出异常而不是您的程序进入未定义的行为领域时,它很有用。
我同意对向量的大多数越界访问是程序员的错误(在这种情况下,您应该使用assert
来更轻松地定位这些错误;标准库的大多数调试版本会自动为您执行此操作)。 您不想使用可以被上游吞噬的异常来报告程序员错误:您希望能够修复错误。
由于对向量的越界访问不太可能是正常程序流程的一部分(在这种情况下,您是对的:事先检查size
而不是让异常冒泡),我同意您的诊断: at
基本上没用。
如果您有指向向量的指针,则at
会更清楚:
return pVector->at(n);
return (*pVector)[n];
return pVector->operator[](n);
撇开性能不谈,首先是更简单、更清晰的代码。
使用 vector::at 比使用 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
在这种情况下,很难检查您是否不恰当地到达了输入的末尾,因为这非常依赖于遇到的确切标记。 在每个使用点进行显式检查是痛苦的,并且随着前/后增量、使用点的偏移、关于某些早期测试的持续有效性的错误推理等,程序员错误的空间更大。
在调试版本中,不能保证at()
比operator[]
慢; 我希望它们的速度大致相同。 不同之处在于at()
准确地指定了边界错误(异常)时会发生什么,而在operator[]
的情况下,它是未定义的行为——我使用的所有系统(g++ 和VC++),至少在使用正常调试标志时。 (另一个区别是,一旦我确定我的代码,我就可以通过关闭调试来显着提高operator[]
的速度。如果性能需要它 - 除非有必要,否则我不会这样做。)
实际上, at()
很少是合适的。 如果上下文使您知道索引可能无效,您可能需要显式测试(例如返回默认值或其他内容),并且如果您知道它不能无效,您想中止(如果你不知道它是否可以无效,我建议你更精确地指定你的函数接口)。 但是,有一些例外,其中无效索引可能是由于解析用户数据而导致的,并且该错误应该导致整个请求中止(但不会使服务器停机); 在这种情况下,例外是合适的,而at()
将为您做到这一点。
注意:似乎有些新人在没有礼貌地说出错误的情况下对这个答案投反对票。 下面的答案是正确的,可以在这里验证。
实际上只有一个区别: at
边界检查而operator[]
没有。 这适用于调试版本和发布版本,并且这在标准中有很好的规定。 就这么简单。
这使得at
方法变慢,但不使用at
也是非常糟糕的建议。 你必须看绝对数字,而不是相对数字。 我可以肯定地打赌,您的大部分代码都在执行比at
更昂贵的操作。 就我个人而言,我尝试使用at
是因为我不希望一个讨厌的错误创建未定义的行为并潜入生产环境。
使用异常的全部意义在于您的错误处理代码可以更远。
在这种特定情况下,用户输入确实是一个很好的例子。 想象一下,您想从语义上分析一个 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()
迭代器。
根据这篇文章,抛开性能不谈,使用at
或operator[]
没有任何区别,仅当访问保证在向量的大小范围内时。 否则,如果访问仅基于向量的容量,则使用at
更安全。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.