向量强制转换中的未定义行为


19

为什么此代码编写了未定义数量的看似未初始化的整数?

#include <iostream>
#include <vector>
using namespace std;


int main()
{
    for (int i : vector<vector<int>>{{77, 777, 7777}}[0])
        cout << i << ' ';
}

我期望输出是77 777 7777

此代码是否应该未定义?

Answers:


18

vector<vector<int>>{{77, 777, 7777}}是临时的,然后vector<vector<int>>{{77, 777, 7777}}[0]在range-for中使用将是不确定的行为。

您应该先创建一个变量,例如

#include <iostream>
#include <vector>
using namespace std;


int main()
{
    auto v = vector<vector<int>>{{77, 777, 7777}};
    for(int i: v[0])
        cout << i << ' ';
}

另外,如果您使用Clang 10.0.0,则会发出有关此行为的警告。

警告:支持指针的对象将在完整表达式[-Wdangling-gsl]向量> {{77,777,7777}} [0]的结尾处被破坏。


2
请使用using std::vector代替,using namespace std;以防止这种不良做法传播。
无限

10

这是因为要迭代的向量将在进入循环之前被销毁。

通常是这样的:

auto&& range = vector<vector<int>>{{77, 777, 7777}}[0];
auto&& first = std::begin(range);
auto&& last = std::end(range);
for(; first != last; ++first)
{
    int i = *first;
    // the rest of the loop
}

问题从第一行开始,因为它的评估如下:

  1. 首先用给定的参数构造向量的向量,该向量成为临时向量,因为它没有名称。

  2. 然后,将范围引用绑定到下标矢量,该矢量仅在包含它的矢量有效的情况下才有效。

  3. 到达分号后,临时向量将被销毁,临时向量将在其析构函数中销毁并重新分配所有存储的向量,包括下标的向量。

  4. 您最终获得对将要迭代的销毁向量的引用。

为避免此问题,有两种解决方案:

  1. 在循环之前声明向量,使其持续到其作用域结束(包括循环)为止。

  2. C ++ 20附带了一个init语句,用于解决这些问题,如果希望立即在循环之后销毁向量,则它比第一种方法要好:

    for (vector<vector<int>> vec{{77, 777, 7777}}; int i : vec[0])
    {
    }

这不是“通常”发生的情况。该标准要求遵循标准规则,要求这种确切的行为(加上适当的作用域和有关命名的注意事项)。
Konrad Rudolph

我指的是一生。即使您手工编写相同的代码,也只能保证获得所需的行为,并且编译器将尽其所能进行优化
dev65

TIL C ++ 20声明范围语法。不知道是快乐还是悲伤。
带有翅膀的小行星

6
vector<vector<int>>{{77, 777, 7777}}[0]

我希望这是悬而未决的。

尽管“ range-for”的定义可确保结肠的RHS在整个过程中保持“活动”状态,但您仍在使用临时模式。仅保留下标的结果,但该结果是引用,并且实际向量无法在声明其的完整表达式后幸存。那不能描述整个循环。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.