考虑以下使用Qt容器类QMap
的C++代码:
#include <QMap>
#include <iostream>
QMap<int, int> myMap;
int count() {
return myMap.size();
}
int main() {
myMap[0] = count();
std::cout << myMap[0] << std::endl;
return 0;
}
根据myMap
在执行count()
之前还是之后是否在其中创建了新条目,此代码的输出将分别1
或0
。
此代码的输出是否取决于QMap
的实现?或者C++规范是否保证何时执行与QMap::operator[]
相关的count()
?或者结果是否不确定,这是最好避免的情况?
我问是因为我在我正在研究的程序中遇到了基本相同的情况。当我在Windows中编译程序并使用库存的Qt 5.5.1 DLL运行它时,结果是0
。但是,当我使用从源代码编译的另一组 Qt 5.5.1 DLL 运行它时,结果是1
。这是一个非常令人困惑的错误,我花了一点时间来追踪,特别是因为我根据运行可执行文件的位置得到了不同的结果!
我希望我能理解同一个程序有两种不同的行为,以便将来能够避免这样的错误。
你的问题在这里:
myMap[0] = count();
是整个赋值是一个表达式,对count()
的调用是一个子表达式。表达式和子表达式之间没有序列点。
这不是关于评估顺序,而是关于副作用的顺序。赋值有副作用,在这种情况下,它会向您QMap
添加一个新元素。只有在序列点上,您才能保证在序列点之前由代码产生的所有副作用都已完成。
函数调用是一个序列点,但它介于函数参数的计算和实际调用之间 - 与返回值无关。由于您在这里没有任何论据,因此在这种情况下不适用。
所以是的,这是未定义的行为,你应该避免它。作为参考,这里有一个关于序列点主题的非常详尽的答案。
解决方案当然很简单:使用两个单独的语句。语句的结尾(;
(总是一个序列点。
可以首先评估作业的任何一方。它类似于函数调用(实际上很可能是函数调用,如果您重载了赋值运算符(,例如:
void f( X a, X b );
其中 A 或 B 可以首先评估。
这不一定特定于特定的编译器 - 同一编译器可能会在不同情况下选择不同的计算顺序。
不幸的是,据我所知,与 Python 等语言不同,无论是给定的编译器、编译器版本还是库,都没有定义的求值顺序。您的代码依赖于未定义的行为。
但是,编译器必须遵守一些规则:这不是其中之一。他们给出的关于未定义行为的示例类似于您的示例:
a[i] = i++;