C++ コア ガイドライン:式のルール

今日の投稿は表現についてです。複雑な式は避け、算術式または論理式の優先順位規則を理解し、式の評価順序を知っておく必要があります。式の誤った優先順位規則を念頭に置いているか、単に間違っているか保証されていない式の評価順序を想定していることが、未定義の動作の主な理由です。私はそれが消化することがたくさんあることを知っています.始めましょう。

これが今日の 4 つのルールです。

  • ES.40:複雑な表現を避ける
  • ES.41:演算子の優先順位について疑問がある場合は、括弧で囲みます
  • ES.43:評価順序が定義されていない式を避ける
  • ES.44:関数の引数の評価順序に依存しない

優先順位と評価のルールは、思ったほど簡単ではありません。それらは C++17 でも変わります。したがって、単純に始める必要があります。

ES.40:複雑な式を避ける

複雑 とはどういう意味ですか?ガイドラインの元の例は次のとおりです:

// bad: assignment hidden in subexpression (1)
while ((c = getc()) != -1)

// bad: two non-local variables assigned in a sub-expressions (1)
while ((cin >> c1, cin >> c2), c1 == c2)

// better, but possibly still too complicated (1)
for (char c1, c2; cin >> c1 >> c2 && c1 == c2;)

// OK: if i and j are not aliased (2)
int x = ++i + ++j; 

// OK: if i != j and i != k (2)
v[i] = v[j] + v[k];

// bad: multiple assignments "hidden" in subexpressions (1)
x = a + (b = f()) + (c = g()) * 7;

// bad: relies on commonly misunderstood precedence rules (1)
x = a & b + c * d && e ^ f == 7;

// bad: undefined behavior (3)
x = x++ + x++ + ++x;

それにいくつかの(数字)を追加しました。まず、(1) を持つすべての式は不適切なスタイルであり、コード レビューに合格するべきではありません。たとえば、x =a &b + c * d &&e ^ f ==7; で何が起こっているか知っていますか。もちろん、演算子の優先順位を調べる必要があります。次のルールでそれについて説明します。式(2)は、条件が満たされていれば問題ないかもしれません。 i と j は論理和でなければならず、インデックス i,j と i,k はペア単位で論理和でなければなりません.

(3) は、どの x が最初に評価されるかが定義されていないため、未定義の動作です。なんで?引数は、最後のセミコロン「;」です。はシーケンス ポイントであり、シーケンス内の以前の評価からのすべての副作用が完全であることが保証されています。

C++17 では、演算子の優先順位の規則が変更されました。代入での右から左への場合を除き、式は左から右です。それについては ES.43 で書きます。

ES.41:演算子の優先順位に疑問がある場合は、括弧を付けてください

一方では、ガイドラインは次のように述べています。演算子の優先順位について疑問がある場合は、括弧 (1) を使用してください。一方、彼らは次のように述べています:ここで括弧を必要としないことを十分に知っている必要があります (2):

const unsigned int flag = 2;
unsigned int a = flag;

if (a & flag != 0) // bad: means a&(flag != 0) (1)

if (a < 0 || a <= max) { // good: quite obvious (2)
 // ...
}

わかった。専門家にとっては (1) の式は明白かもしれませんが、初心者にとっては (2) の式は難しいかもしれません。

ガイドラインに従って、心に留めているヒントは 2 つだけです:

<オール>
  • 優先順位について疑問がある場合は、括弧を使用してください。初心者もお忘れなく!
  • cppreference.com の優先順位表を枕元に置いておいてください。
  • すぐにルール ES.43 と ES.44 にジャンプし、ルール ES.42 については次の投稿で書きます。 C++17 では、式の評価の順序が変更されました。

    ES.43:評価順序が定義されていない式を避ける

    C++14 では、次の式の動作は未定義です。

    v[i] = ++i; // the result is undefined
    

    これは C++17 には当てはまりません。 C++17 では、最後のコード スニペットの評価順序は右から左です。したがって、式の動作は明確に定義されています。

    C++17 に関する追加の保証は次のとおりです。

    <オール>
  • 後置式は左から右に評価されます。これには、関数呼び出しとメンバー選択式が含まれます。
  • 代入式は右から左に評価されます。これには複合課題が含まれます。
  • シフト演算子へのオペランドは、左から右に評価されます。
  • これが最初の提案の文言でした。彼らはまた、いくつかの例を提供しました。

    a.b
    a->b
    a->*b
    a(b1, b2, b3) // (1)
    b @= a
    a[b]
    a << b
    a >> b
    

    これらの例をどのように読むべきですか?とてもシンプルです。最初に a が評価され、次に b、c、d が評価されます。

    式 (1) は少しトリッキーです。 C++17 では、関数がその引数の前に評価されるという保証しかありませんが、引数の評価の順序はまだ指定されていません.

    最後の文が簡単ではなかったことは知っています。もう少し詳しく説明しましょう。

    ES.44:関数引数の評価順序に依存しない

    ここ数年、関数の引数の評価順序は左から右であると開発者が想定していたため、多くのエラーが発生しました。違う!保証はありません!

    #include <iostream>
    
    void func(int fir, int sec){
     std::cout << "(" << fir << "," << sec << ")" << std::endl;
    }
    
    int main(){
     int i = 0;
     func(i++, i++);
    }
    

    これが私の証拠です。 gcc と clang からの出力は異なります:

    • gcc:

    • クラン

    C++17 では、この動作は変わりませんでした。評価の順序は指定されていません。ただし、少なくとも、次の式の評価の順序は C++17 で指定されています。

    f1()->m(f2()); // evaluation left to right (1)
    cout << f1() << f2(); // evaluation left to right (2)
    
    f1() = f(2); // evaluation right to left (3)
    

    理由は次のとおりです。

    (1):後置式は左から右に評価されます。これには、関数呼び出しとメンバー選択式が含まれます。

    (2):シフト演算子へのオペランドは左から右に評価されます。

    (3):代入式は右から左に評価されます。

    念のため。 C++14 では、最後の 3 つの式の動作が未定義です。

    次は?

    確かに、これは非常にやりがいのある投稿でしたが、優れたプログラマーになるために克服しなければならない課題です。次回の投稿の主なトピックは、キャスト操作についてです。