在C++中,当表达式涉及对象时,将表达式赋值到对象中时,是否有定义的操作顺序?



考虑以下使用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()之前还是之后是否在其中创建了新条目,此代码的输出将分别10

此代码的输出是否取决于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++; 

最新更新