在 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()
之前或之后在其中创建了新条目,此代码的输出将分别为 1
或 0
。
此代码的输出是否取决于 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.
解决办法当然很简单:使用两个单独的语句。语句的结尾 (;
) 始终是一个序列点。
考虑以下使用 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()
之前或之后在其中创建了新条目,此代码的输出将分别为 1
或 0
。
此代码的输出是否取决于 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.
解决办法当然很简单:使用两个单独的语句。语句的结尾 (;
) 始终是一个序列点。