从Iterator读取会在下一个参数中增加之前产生新值

时间:2013-12-15 00:22:29

标签: c++ iterator stdvector operator-precedence

我试图在某个图表中总结一条封闭路径的距离。存储为表示节点的int s的向量的路径以0开头,并且应该具有从最后一个节点返回到0的另一条边。所以我正在做

using namespace std;
typedef vector<int> Route;

// r is some Route, g is some Graph
int distance = 0;
for (Route::iterator it = r.begin(); it != r.end(); ) {
    clog << "it: " << *it << endl;
    distance += g.dist(*it, (++it == r.end()) ? 0 : *it);
}

然而,我的dist方法的第一个参数是接收错误的值。上面一行中的日志记录确实输出了正确的值,但是从dist记录参数显示它确实传递了两次相同的值

在最后一次迭代中,它确实被传递0,这是完全错误的。有时候,对于更大的向量(我有120个航点的另一个输入数据),第一个参数变为一些深奥的大值(它打破了距离函数),而第二个参数是0(假设)。

这里发生了什么以及为什么会发生这种情况?我现在已经用第一个参数的临时变量解决了这个问题,但它似乎很丑陋且不必要。


为了完整性,这里是我的dist方法的代码,它是Graph类的一部分(基本上是一个邻接矩阵):

int dist(size_t x, size_t y) {
    clog<<"distance: "<<x<<" "<<y<<endl;
    if (x == y) return 0;
    if (x < y) return dist(y, x); // edges are symmetric
    if (x > size)
        throw out_of_range("Graph::dist: index too large");
    return triangle[x*(x-1)/2+y]; // stored in a flat array representing the lower half of the matrix
}

这是一个小例子(vector [0,1,2,3])的输出:

it: 0
distance: 1 1
it: 1
distance: 2 2
it: 2
distance: 3 3
it: 3
distance: 0 0
          ^^^ the distance arguments are wrong

对于我正在获得的大型例子

[…]
distance: 32 32
it: 32
distance: 51 51
it: 51
distance: 90 90
it: 90
distance: 12 12
it: 12
distance: 859381811 0
          ^^^^^^^^^ WTH?
terminate called after throwing an instance of 'std::out_of_range'
  what():  Graph::dist: index too large

我原本希望得到(对于小例子):

it: 0
distance: 0 1
it: 1
distance: 1 2
it: 2
distance: 2 3
it: 3
distance: 3 0

1 个答案:

答案 0 :(得分:2)

当您在函数调用中使用*时,编译器进行了优化。以下作品:

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

int dist(size_t x, size_t y) {
    cout<<"distance: "<<x<<" "<<y<<endl;
    return y - x;
}

int main() {
  // r is some Route, g is some Graph
  vector<int> r = {1, 2, 3, 4};
  int distance = 0;
  for (auto it = r.begin(); it != r.end(); ) {
      cout << "it: " << *it << endl;
      int prev = *it;
      ++it;    
      int current = (it == r.end()) ? 0 : *it;
      distance += dist(prev, current);
  }
}

我仍在挖掘标准并试图弄清楚为什么允许这种优化。评估订单并没有真正解释它。

==========编辑===========================

我错了。评估顺序是问题的原因。在这种情况下,您的三元运算符以正确的顺序进行评估,但gcc也可以自由地在三元运算符的子表达式周围移动。代码的汇编如下所示:

  movq  %rax, %rdi  # tmp96,
  call  _ZN9__gnu_cxx17__normal_iteratorIPiSt6vectorIiSaIiEEEppEv #
  leaq  -32(%rbp), %rdx #, tmp97
  movq  %rdx, %rsi  # tmp97,
  movq  %rax, %rdi  # D.33388,
  call  _ZN9__gnu_cxxeqIPiSt6vectorIiSaIiEEEEbRKNS_17__normal_iteratorIT_T0_EESA_ #

      # If the test succeeds, go to fetch the content of the iterator
      # otherwise just set it to zero and proceed.
  testb %al, %al  # D.33389
  je  .L5 #,
  movl  $0, %ebx  #, iftmp.2
  jmp .L6 #
.L5:
  leaq  -64(%rbp), %rax #, tmp98
  movq  %rax, %rdi  # tmp98,

      # fetch the content of the iterator if we need it
  call  _ZNK9__gnu_cxx17__normal_iteratorIPiSt6vectorIiSaIiEEEdeEv  #
  movl  (%rax), %eax  # *D.33393_16, D.33394
  movslq  %eax, %rbx  # D.33394, iftmp.2
.L6:
  leaq  -64(%rbp), %rax #, tmp99
  movq  %rax, %rdi  # tmp99,

      # deference the iterator again. NOTE iterator here was NOT INTCREMENTED
  call  _ZNK9__gnu_cxx17__normal_iteratorIPiSt6vectorIiSaIiEEEdeEv  #
  movl  (%rax), %eax  # *D.33395_19, D.33396
  cltq
  movq  %rbx, %rsi  # iftmp.2,
  movq  %rax, %rdi  # D.33397,
  call  _Z4distmm #
  addl  %eax, -24(%rbp) # D.33398, distance

注意*它的评价。在这种情况下,如果需要,迭代器会递增,评估三元运算符条件,然后取消引用 it

============编辑===========

C ++中对函数参数及其子表达式的评估顺序是未定义的。更多信息可以在function parameter evaluation order找到。关于评估顺序written on cppreference

的详细信息,还有一个非常好的技巧