web-dev-qa-db-ja.com

メソッドチェーンでのC ++の実行順序

このプログラムの出力:

_#include <iostream> 
class c1
{   
  public:
    c1& meth1(int* ar) {
      std::cout << "method 1" << std::endl;
      *ar = 1;
      return *this;
    }
    void meth2(int ar)
    {
      std::cout << "method 2:"<< ar << std::endl;
    }
};

int main()
{
  c1 c;
  int nu = 0;
  c.meth1(&nu).meth2(nu);
}
_

は:

_method 1
method 2:0
_

meth2()の開始時にnuが1ではないのはなぜですか?

103
Moises Viñas

評価順序が指定されていないため。

nu内のmain0の前meth1が呼び出されます。これが連鎖の問題です。しないことをお勧めします。

素敵で、シンプルで、明確で、読みやすく、理解しやすいプログラムを作成するだけです。

int main()
{
  c1 c;
  int nu = 0;
  c.meth1(&nu);
  c.meth2(nu);
}

評価の順序に関する ドラフト標準 のこの部分は関連があると思います。

1.9プログラム実行

...

  1. 特に明記されていない限り、個々の演算子のオペランドと個々の式の部分式の評価は順序付けられていません。演算子のオペランドの値計算は、演算子の結果の値計算の前に順序付けられます。 スカラーオブジェクトの副作用が、同じスカラーオブジェクトの別の副作用または同じスカラーオブジェクトの値を使用した値の計算と比較して順序付けられておらず、潜在的に並行していない場合、動作は未定義です

また:

5.2.2関数呼び出し

...

  1. [注:後置式および引数の評価は、すべて互いに相対的ではありません。引数評価のすべての副作用は、関数の前に順序付けされます。が入力されている-終了ノート]

したがって、行c.meth1(&nu).meth2(nu);では、meth2への最後の呼び出しの関数呼び出し演算子の観点から演算子で何が起こっているのかを考えてください。したがって、後置式と引数nuの内訳を明確に確認できます。 :

operator()(c.meth1(&nu).meth2, nu);

後置式と引数の評価最終関数呼び出し(つまり、後置式c.meth1(&nu).meth2nu)の値は相互に関連して順序付けられていません上記のfunction callルールに従って。したがって、スカラーオブジェクトarに対する後置式の計算の副作用は、meth2関数呼び出しの前のnuの引数評価に関連して順序付けられません。 。上記のプログラム実行ルールにより、これは未定義の動作です。

言い換えると、コンパイラがmeth2呼び出しの後にmeth1呼び出しに対するnu引数を評価する必要はありません-meth1の副作用がnu評価に影響しないと仮定することは自由です。

上記によって生成されたアセンブリコードには、main関数に次のシーケンスが含まれています。

  1. 変数nuはスタックに割り当てられ、0で初期化されます。
  2. レジスタ(私の場合はebx)はnuの値のコピーを受け取ります
  3. nuおよびcのアドレスがパラメーターレジスタにロードされます。
  4. meth1が呼び出されます
  5. 戻り値レジスタとnuレジスタのebxの-​​以前にキャッシュされた値がパラメータレジスタにロードされます。
  6. meth2が呼び出されます

重大なことに、上記のステップ5で、コンパイラーは、ステップ2のnuのキャッシュされた値をmeth2の関数呼び出しで再使用できるようにします。ここでは、numeth1の呼び出しによって変更された可能性を無視しています-'undefined behaviour' in action。

注:この回答は実質的に元の形式から変更されています。オペランド計算の副作用に関する最初の説明は、最後の関数呼び出しの前に順序付けされていないため、正しくありませんでした。問題は、オペランド自体の計算が不定に順序付けられているという事実です。

28
Smeeheey

1998 C++標準のセクション5、パラ4

特に明記されている場合を除き、個々の演算子のオペランドと個々の式の部分式の評価の順序、および副作用が発生する順序は指定されていません。前のシーケンスポイントと次のシーケンスポイントの間で、スカラーオブジェクトは、式の評価によって、格納された値を最大で1回変更します。さらに、保存する値を決定するためにのみ、以前の値にアクセスします。この段落の要件は、完全な式の部分式の許容される順序ごとに満たされるものとします。それ以外の場合、動作は未定義です。

(この質問に関係のない脚注#53への参照は省略しました)。

基本的に、&nuc1::meth1()を呼び出す前に評価する必要があり、nuc1::meth2()を呼び出す前に評価する必要があります。ただし、nu&nuの前に評価する必要はありません(たとえば、nuを最初に評価し、次に&nu、次にc1::meth1()を評価することは許可されますと呼ばれます-これはコンパイラが行っていることかもしれません)。したがって、c1::meth1()の式*ar = 1は、main()に渡されるために、c1::meth2()nuが評価される前に評価されることは保証されません。

後のC++標準(今夜使用しているPCには現在ありません)には、基本的に同じ条項があります。

9
Peter

コンパイル時に、関数meth1とmeth2が実際に呼び出される前に、パラメーターが渡されたと思います。 「c.meth1(&nu).meth2(nu);」を使用する場合値nu = 0がmeth2に渡されているため、「nu」が後で変更されるかどうかは関係ありません。

これを試すことができます:

#include <iostream> 
class c1
{
public:
    c1& meth1(int* ar) {
        std::cout << "method 1" << std::endl;
        *ar = 1;
        return *this;
    }
    void meth2(int* ar)
    {
        std::cout << "method 2:" << *ar << std::endl;
    }
};

int main()
{
    c1 c;
    int nu = 0;
    c.meth1(&nu).meth2(&nu);
    getchar();
}

それはあなたが望む答えを得るでしょう

6
T-shirt saintor