C++ 为什么是'--++;a-&x200B;-+++;b--';按这个顺序计算?

C++ 为什么是'--++;a-&x200B;-+++;b--';按这个顺序计算?,c++,operator-overloading,compiler-optimization,evaluation,operator-precedence,C++,Operator Overloading,Compiler Optimization,Evaluation,Operator Precedence,为什么下面打印的是bD aD aB aA aC aU而不是aD aB aA aC bD aU?换句话说,为什么b--在--++a--++之前进行计算 #include <iostream> using namespace std; class A { char c_; public: A(char c) : c_(c) {} A& operator++() { cout << c_ << "A ";

为什么下面打印的是
bD aD aB aA aC aU
而不是
aD aB aA aC bD aU
?换句话说,为什么
b--
--++a--++
之前进行计算

#include <iostream>
using namespace std;

class A {
    char c_;
public:
    A(char c) : c_(c) {}
    A& operator++() {
        cout << c_ << "A ";
        return *this;
    }
    A& operator++(int) {
        cout << c_ << "B ";
        return *this;
    }
    A& operator--() {
        cout << c_ << "C ";
        return *this;
    }
    A& operator--(int) {
        cout << c_ << "D ";
        return *this;
    }
    void operator+(A& b) {
        cout << c_ << "U ";
    }
};

int main()
{
    A a('a'), b('b');
    --++a-- ++ +b--;  // the culprit
}
#包括
使用名称空间std;
甲级{
char c_;
公众:
A(char c):c_uz(c){}
运算符++(){

Cuth> P>没有C++中的某些东西,它表示需要以这种方式来评估事物。C++有一个概念,在某些操作之前保证有其他操作发生。这是一个偏序集;也就是说,SSOOM运算是在其他的顺序之前进行的,两个运算不能在其他的之前进行排序,a。nd如果a在b之前排序,b在c之前排序,那么a在c之前排序。然而,有许多类型的操作在保证之前没有排序。在c++11之前,有一个序列点的概念,它不是完全相同,而是相似的

很少有操作符(我相信只有
&&
?:
|
)保证它们的参数之间有一个序列点(即使在C++17之前,当操作符重载时,这种保证也不存在)编译器可以自由地先计算左侧,先计算右侧,或者(我认为)甚至可以同时计算它们


有时更改优化选项可能会更改结果或更改编译器。显然,您没有看到这一点;这里没有任何保证。

运算符优先级和关联性规则仅用于将表达式从原始的“表达式中的运算符”表示法转换为等效的“函数调用”格式。转换后,您将得到一系列嵌套函数调用,这些调用以通常的方式进行处理。特别是,参数求值顺序未指定,这意味着无法确定“binary+”调用的哪个操作数将首先求值

另外,请注意,在您的例子中,二进制
+
被实现为一个成员函数,这在其参数之间造成了某种表面上的不对称:一个参数是“正则”参数,另一个是
this
。可能一些编译器“更喜欢”计算“正则”首先是参数,这会导致在测试中首先对
b--
求值(如果将二进制
+
作为独立函数实现,那么同一个编译器的顺序可能会不同)。或者,这根本不重要


例如,Clang从计算第一个操作数开始,将
b--
留待以后使用。

表达式语句

--++a-- ++ +b--;  // the culprit
可以用以下方式表示

起初喜欢

( --++a-- ++ )  + ( b-- );
然后喜欢

( -- ( ++ ( ( a-- ) ++ ) ) )  + ( b-- );
最后就像

a.operator --( 0 ).operator ++( 0 ).operator ++().operator --().operator  + ( b.operator --( 0 ) );
这是一个演示程序

#include <iostream>
using namespace std;

#include <iostream>
using namespace std;

class A {
    char c_;
public:
    A(char c) : c_(c) {}
    A& operator++() {
        cout << c_ << "A ";
        return *this;
    }
    A& operator++(int) {
        cout << c_ << "B ";
        return *this;
    }
    A& operator--() {
        cout << c_ << "C ";
        return *this;
    }
    A& operator--(int) {
        cout << c_ << "D ";
        return *this;
    }
    void operator+(A& b) {
        cout << c_ << "U ";
    }
};

int main()
{
    A a('a'), b('b');
    --++a-- ++ +b--;  // the culprit

    std::cout << std::endl;

    a.operator --( 0 ).operator ++( 0 ).operator ++().operator --().operator  + ( b.operator --( 0 ) );

    return 0;
}
您可以想象最后一个以函数形式编写的表达式就像该形式的后缀表达式一样

postfix-expression ( expression-list ) 
后缀表达式在哪里

a.operator --( 0 ).operator ++( 0 ).operator ++().operator --().operator  +
表达式列表是

b.operator --( 0 )

在C++标准(5.2.2函数调用)中,有人说

8[注:对FIX后表达式和参数的评估 都是不连续的相互关系。所有的侧面效应 参数求值在输入函数之前按顺序排列(请参见 1.9)。-结束注释]


因此,一开始是对参数求值还是对后缀表达式求值是由实现定义的。根据显示的输出,编译器首先对参数求值,然后才对后缀表达式求值。

我认为他们错误地包含了这样一个问题

除注明外,以下摘录均来自N4618的§[intro.execution](我认为这些内容在最近的草稿中没有任何变化)

第16段的基本定义是在
之前排序的,
不确定排序的
,等等

第18段说:

除非另有说明,否则对单个运算符的操作数和单个表达式的子表达式的求值是不排序的

在本例中,您(间接)调用了一些函数。其中的规则也相当简单:

调用函数时(无论函数是否内联),与任何参数表达式或与指定被调用函数的后缀表达式相关联的每个值计算和副作用,在执行被调用函数体中的每个表达式或语句之前进行排序。对于每个函数调用F,对于F中发生的每个计算A和不在F中出现,但在同一线程上进行计算,并且作为同一信号处理程序(如果有)的一部分,A是 在B之前排序或B在A之前排序

将其放在要点中,以更直接地指示顺序:

  • 首先计算函数参数,以及指定被调用函数的参数
  • 评估函数本身的主体
  • 计算另一个(子)表达式
  • 除非某个线程启动,以允许其他线程并行执行,否则不允许交错

    那么,在我们通过操作符重载而不是直接调用函数之前,这些是否发生了变化?第19段说“否”:

    被调用函数的执行顺序约束(如上所述)是所评估的函数调用的特征,无论调用函数的表达式的语法是什么

    §[expr]/2还说:

    重载运算符的使用被转换为函数调用,如下所述 在13.5中,重载运算符遵守第5条中规定的语法和求值顺序规则,但操作数类型和值类别的要求被函数调用规则所取代

    个体经营者 唯一的行动
    b.operator --( 0 )
    
    --++a--+++b--;//will follow with
    --++a+++b--;//and so on
    --++a+b--;
    --++a+b;
    --a+b;
    a+b;