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]