在 C++ 中,当表达式涉及该对象时,将表达式分配给对象时是否有定义的操作顺序?

In C++, is there a defined order of operations when assigning an expression into an object when the expression involves that object?

考虑以下使用 Qt 容器的 C++ 代码 class QMap:

#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++ 规范是否对 count() 相对于 QMap::operator[] 的执行时间做出任何 gua运行 的规定?或者结果可能是未定义的,这是最好避免的情况?

我问是因为我在我正在处理的程序中遇到了基本相同的情况。当我使用常用​​的 Qt 5.5.1 DLL 在 Windows 和 运行 中编译程序时,结果是 0。但是,当我 运行 它使用另一组从源代码编译的 Qt 5.5.1 DLL 时,结果是 1。这是一个非常令人困惑的错误,我花了一些时间来追踪,特别是因为我得到了不同的结果,具体取决于我 运行 可执行文件的位置!

我希望我能理解为什么同一个程序会有两种不同的行为,这样我以后就可以避免这样的错误。

可以先评估作业的任何一方。它类似于函数调用(如果您重载了赋值运算符,实际上很可能是函数调用)例如:

  void f( X a, X b );

其中可以先计算 a 或 b。

这不一定特定于特定的编译器 - 相同的编译器可能会在不同的情况下选择不同的评估顺序。

不幸的是,据我所知,与 Python 这样的语言不同,对于给定的编译器、编译器版本或库,评估顺序没有定义的顺序。您的代码取决于未定义的行为。

但是,编译器必须遵守一些 rules:这不是其中之一。他们给出的关于未定义行为的示例类似于您的示例:

a[i] = i++; 

您的问题:

myMap[0] = count();

是整个赋值是一个表达式,而对count()的调用是一个子表达式。表达式和子表达式之间没有序列点

这不是关于评估顺序,而是关于副作用的顺序。赋值具有 副作用 ,在这种情况下,它会向您的 QMap 添加一个新元素。只有在 序列点 ,您才能保证所有 副作用 都是由 序列点 [=38= 之前的代码产生的] 完成。

函数调用一个序列点,但它介于函数参数的计算和实际调用之间——与return值无关。因为你在这里没有任何参数,所以它不适用于这种情况。

所以是的,这是未定义的行为,您应该避免它。作为参考,here's a quite exhaustive answer on the topic of sequence points.


解决办法当然很简单:使用两个单独的语句。语句的结尾 (;) 始终是一个序列点。