我试图在某个图表中总结一条封闭路径的距离。存储为表示节点的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
答案 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
的详细信息,还有一个非常好的技巧