Sequenced before

维基教科书,自由的教学读本

先序于”规则(Sequenced before)是C++11语言的表达式求值时,子表达式计算顺序的规则。在C++11标准之前,C++采用顺序点方法确定表达式求值顺序。[1]

定义[编辑]

表达式求值包括两部分:

  • 值计算: 包括确定对象实体(广义左值glvalue求值,如表达式返回某些对象的引用)、读取以前赋给一个对象的值(纯右值,如表达式返回一个数或其他值)
  • 开始副效应:包括访问(读写)一个指向volatile左值的对象、修改(写)一个对象、调用库I/O函数、调用包含了这些操作的任何函数。

“先序于”规则是反对称的、传递的、同一个线程中的求值间的全序关系。包括下述情况:

  • 如果A先序于B,则A的求值完成后才可能开始对B求值
  • 如果A非先序于B,且B非先序于A,则存在两种可能:
    • A与B的求值是序列化的:二者可以按照任意顺序求值或者重叠求值(即使单线程执行,编译器可以把A与B的机器指令交错执行)
    • A与B的求值是不确定序列化的:二者可以按照任意顺序求值但不能重叠求值,或者A完全在B之前,或者B完全在A之前。同一个表达式下次求值时其顺序可以相反。

规则[编辑]

  • 完全表达式是先序于下一个完全表达式。完全表达式(full expression)包括了:
    • 不求值表达式(unevaluated operand):运算符typeid、 sizeof、 noexcept、decltype的运算子,作为表达式不需要求值。例如std::size_t n = sizeof(std::cout << 42);
    • 常量表达式(constant expression):编译时可求值
    • 立即调用(immediate invocation,自C++20英语C++20开始),即关键字consteval
    • 整个初始化器,包括逗号分隔的成分表达式
    • 析构函数调用,由非临时对象在生命期结束时产生
    • 一个表达式,不是别的完全表达式的一部分(如整个表达式语句,for/while循环的控制表达式,if/switch的表达式,返回语句表达式,等等),包括表达式结果的隐式转换,临时对象的析构、缺省成员初始化器(当初始化聚集类型)、其他语言结构涉及函数调用
  • 任何运算符的操作数的值计算(不包括副作用) 是先序于运算符最终结果的的值计算(不包括副作用) 。
  • 调用函数时(无论是否为内联函数或显式函数调用),实参表达式以及指向被调函数的后缀表达式的值计算与副作用,总是先序于被调函数体内的每个表达式或语句。
  • 内建的后增、后减运算符的值计算是先序于的副作用。[2]
  • 内建的先增、先减运算符的副作用是先序于的值计算。
  • 内建的逻辑与运算符“&&“,以及内建的逻辑或运算符“||”的第一个(左)运算数的值计算与副作用是先序于第二个(右)运算数的值计算与副作用。
  • 条件运算符?:的第一个表达式的值计算与副作用是先序于第二个表达式或第三个表达式的值计算与副作用。
  • 内建赋值运算符的副作用(修改左操作数),以及所有内建复合赋值运算符,是后序于左操作数与右操作数的值计算(但不包括副作用),并且是先序于赋值表达式的值计算(即返回修改后的对象的引用)。
  • 内建逗号运算符的第一(左)操作数的值计算与副作用是先序于第二(右)操作数。
  • 花括号初始化列表中,每个给定的初始化子句的值计算与副作用是先序于其后的初始化列表中任意初始化子句的值计算与副作用。 也即,初始化列表中逗号分隔的各个子句是严格从左至右依次完成值计算与副作用。
  • 一个函数调用如果不是先序于也不是后序于另一个函数调用,则两个函数调用是不确定排序(Indeterminately Sequenced)(两个函数的CPU指令不能交错,即使是内联函数)。例外情况,标准库算法在std::execution::par_unseq执行政策的函数调用是无顺序化的,可任意交错(从C++17)
  • 运算符new的内存分配器调用与new表达式的构造函数实参求值,以前是不确定序列化;从C++17开始,前者是先序于后者,即先分配好内存,然后对构造函数的实参求值。
  • 从C++14,对于函数返回值,函数调用最终结果的临时对象的拷贝初始化是先序于返回语句操作数的所有临时对象的析构,而这些又是先序于返回语句所在block内的局部变量的析构。
  • 函数调用表达式中,函数名字表达式是先序于每个实参表达式与每个缺省实参。
  • 函数调用表达式中,形参初始化的值计算与副作用,实参之间是不确定序列化。
  • 重载运算符如果是用运算符方式调用,则遵守它所重载的内建运算符的序列化规则。
  • 对于下标表达式E1[E2],E1的值计算与副作用是先序于E2的值计算与副作用。
  • 对于成员指针表达式E1.*E2E1->*E2,E1的值计算与副作用是先序于E2的值计算与副作用。
  • 对于移位运算符表达式E1<<E2E1>>E2,E1的值计算与副作用是先序于E2的值计算与副作用。
  • 对于简单赋值运算符表达式E1=E2与每个复合赋值运算符表达式E1@=E2,E2的值计算与副作用是先序于E1的值计算与副作用。
  • 圆括号初始化列表中,逗号分隔的表达式的求值如同函数调用,即不确定序列化。

参考文献[编辑]

  1. "Order of evaluation" in cppreference.com
  2. C++11 standard (ISO/IEC 14882:2011):5.3.4 New [expr.new]