双端队列问题和索引运算符超出双端队列大小

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 的边界,如果该范围的代码中没有错误,几乎不可能以这种方式意外索引出界。