双端队列问题和索引运算符超出双端队列大小
issues with deques and exceeding deque size with index operator
C++ 中的双端队列有一个奇怪的问题。
假设我有一个大小为 4 的双端队列。出于某种原因,在使用索引运算符时,我似乎能够超过双端队列的大小。
换句话说,如果我写以下内容,编译器和执行程序都不会吐:
for(int i = 0; i < 7; i++)
{
x[i] = (double)(i*i);
cout << x[i] << endl;
}
其中 x 是双端队列。我实际上能够从中获得输出。
它不会增加双端队列的大小。如果我输出 x.size(),我仍然得到 4。
是什么赋予了?
我正在使用 Code::Blocks 和它附带的标准默认 gcc 编译器。
operator[]
不进行边界检查,就像使用原始数组时一样。 at
成员函数可以,如果您改为使用
x.at(i);
如果您超出 deque
的界限,您将得到一个 std::out_of_range
异常。如果您通过内存错误检查器(如 valgrind)运行 原始代码,您将看到 "invalid read" 和 "invalid write" 错误。
如果您查看 cppreference's docs on operator[],您会看到备注 "No bounds checking is performed."
然而 the docs for at() 说
If pos not within the range of the container, an exception of type std::out_of_range is thrown
超出容器的界限是未定义的行为。如果您使用不确定索引是否在边界内的索引进行访问,则您的工作是检查它是否在边界内,或者使用 at
并可能处理异常。
越界索引会产生未定义的行为,因此任何事情都有可能发生。
许多容器会将当前大小四舍五入到某个方便的值(例如,2 的幂),因此根据当前大小,您将在集合中的最后一个项目之后拥有一定的内存量。对该内存进行索引并尝试读取它会产生一些结果,但内存通常是未初始化的,因此结果通常是无意义和无效的(而且,尽管大多数情况下不会,容器可以进行边界检查,并抛出异常或者当你索引超出范围时几乎任何其他东西)。
IMO,at
是一个相当糟糕的工具来处理这种可能性。避免此类问题的更好方法是基于范围的 for
循环:
for (auto &d : x) {
d = d * d;
std::cout << d << "\n"; // avoid `endl`, which flushes the stream.
}
另一种可能性是使用标准算法:
std::transform(x.begin(), x.end(), x.begin(), [](double d) { return d*d; });
std::copy(x.begin(), x.end(), std::ostream_iterator<double>(std::cout, "\n"));
还有一些基于范围的算法(例如,Boost 中的一套算法,至少还有一套被建议用于未来的 C++ 标准),它们 (do/would) 允许以下一般顺序的东西:
copy(x, output_range<double>(std::cout, "\n"));
由于这会自行计算出 x
的边界,如果该范围的代码中没有错误,几乎不可能以这种方式意外索引出界。
C++ 中的双端队列有一个奇怪的问题。 假设我有一个大小为 4 的双端队列。出于某种原因,在使用索引运算符时,我似乎能够超过双端队列的大小。 换句话说,如果我写以下内容,编译器和执行程序都不会吐:
for(int i = 0; i < 7; i++)
{
x[i] = (double)(i*i);
cout << x[i] << endl;
}
其中 x 是双端队列。我实际上能够从中获得输出。 它不会增加双端队列的大小。如果我输出 x.size(),我仍然得到 4。 是什么赋予了? 我正在使用 Code::Blocks 和它附带的标准默认 gcc 编译器。
operator[]
不进行边界检查,就像使用原始数组时一样。 at
成员函数可以,如果您改为使用
x.at(i);
如果您超出 deque
的界限,您将得到一个 std::out_of_range
异常。如果您通过内存错误检查器(如 valgrind)运行 原始代码,您将看到 "invalid read" 和 "invalid write" 错误。
如果您查看 cppreference's docs on operator[],您会看到备注 "No bounds checking is performed."
然而 the docs for at() 说
If pos not within the range of the container, an exception of type std::out_of_range is thrown
超出容器的界限是未定义的行为。如果您使用不确定索引是否在边界内的索引进行访问,则您的工作是检查它是否在边界内,或者使用 at
并可能处理异常。
越界索引会产生未定义的行为,因此任何事情都有可能发生。
许多容器会将当前大小四舍五入到某个方便的值(例如,2 的幂),因此根据当前大小,您将在集合中的最后一个项目之后拥有一定的内存量。对该内存进行索引并尝试读取它会产生一些结果,但内存通常是未初始化的,因此结果通常是无意义和无效的(而且,尽管大多数情况下不会,容器可以进行边界检查,并抛出异常或者当你索引超出范围时几乎任何其他东西)。
IMO,at
是一个相当糟糕的工具来处理这种可能性。避免此类问题的更好方法是基于范围的 for
循环:
for (auto &d : x) {
d = d * d;
std::cout << d << "\n"; // avoid `endl`, which flushes the stream.
}
另一种可能性是使用标准算法:
std::transform(x.begin(), x.end(), x.begin(), [](double d) { return d*d; });
std::copy(x.begin(), x.end(), std::ostream_iterator<double>(std::cout, "\n"));
还有一些基于范围的算法(例如,Boost 中的一套算法,至少还有一套被建议用于未来的 C++ 标准),它们 (do/would) 允许以下一般顺序的东西:
copy(x, output_range<double>(std::cout, "\n"));
由于这会自行计算出 x
的边界,如果该范围的代码中没有错误,几乎不可能以这种方式意外索引出界。