++クリスト!
C++ 標準 1.9.16 は、クラスに operator++(postfix) を実装する方法に関して非常に理にかなっています。その operator++(int) メソッドが呼び出されると、それ自体がインクリメントされ、元の値のコピーが返されます。C++ の仕様にあるとおりです。
基準が改善されるのを見るのは素晴らしいことです!
ただし、以前の (ANSI 以前の) C コンパイラを使用したことをはっきりと覚えています。
foo -> bar(i++) -> charlie(i++);
あなたが思うことをしませんでした!代わりに、次と同等にコンパイルされました。
foo -> bar(i) -> charlie(i); ++i; ++i;
そして、この動作はコンパイラの実装に依存していました。(移植を楽しくします。)
最新のコンパイラが正しく動作するようになったことをテストして検証するのは簡単です。
#define SHOW(S,X) cout << S << ": " # X " = " << (X) << endl
struct Foo
{
Foo & bar(const char * theString, int theI)
{ SHOW(theString, theI); return *this; }
};
int
main()
{
Foo f;
int i = 0;
f . bar("A",i) . bar("B",i++) . bar("C",i) . bar("D",i);
SHOW("END ",i);
}
スレッドのコメントに返信しています...
...そして、ほぼ全員の回答に基づいて構築しています... (ありがとう!)
これをもう少し詳しく説明する必要があると思います。
与えられた:
baz(g(),h());
その場合、 g()がh()の前または後 に呼び出されるかどうかはわかりません。「未指定」です。
しかし、 g()とh( )の両方がbaz()の前に呼び出されることはわかっています。
与えられた:
bar(i++,i++);
繰り返しますが、どのi++が最初に評価されるかはわかりません。おそらく、bar()が呼び出される 前にiが 1 回または 2 回インクリメントされるかどうかさえわかりません。結果は未定義です! ( i=0とすると、これはbar(0,0)またはbar(1,0)またはbar(0,1)または何か本当に奇妙なものになる可能性があります!)
与えられた:
foo(i++);
これで、 foo()が呼び出される前にiがインクリメントされることがわかりました。クリストが C++ 標準セクション 1.9.16から指摘したように:
関数を呼び出すとき (関数がインラインであるかどうかに関係なく)、すべての値の計算と、任意の引数式、または呼び出された関数を指定する後置式に関連付けられた副作用は、本体のすべての式またはステートメントの実行前に順序付けられます。と呼ばれる機能。[ 注: 異なる引数式に関連付けられた値の計算と副作用は順序付けされていません。-- 巻末注記】
セクション 5.2.6 の方が適切だと思いますが:
後置 ++ 式の値は、そのオペランドの値です。[ 注: 得られた値は元の値のコピーです -- 末尾の注 ] オペランドは変更可能な左辺値でなければなりません。オペランドの型は、算術型または完全な有効なオブジェクト型へのポインターでなければなりません。オペランド オブジェクトの値は、オブジェクトが bool 型の場合を除き、それに 1 を追加することによって変更されます。bool 型の場合は true に設定されます。[注: この使用は非推奨です。付録 D を参照してください。 -- 末尾の注記] ++ 式の値の計算は、オペランド オブジェクトの変更の前に順序付けられます。不定順序の関数呼び出しに関しては、後置 ++ の操作は単一の評価です。[ 注: したがって、関数呼び出しは、左辺値から右辺値への変換と、単一の後置 ++ 演算子に関連する副作用との間に介在してはなりません。-- 終わりの注]結果は右辺値です。結果の型は、オペランドの型の cv 非修飾バージョンです。5.7 および 5.17 も参照してください。
セクション 1.9.16 の標準には、(例の一部として) 次の項目もリストされています。
i = 7, i++, i++; // i becomes 9 (valid)
f(i = -1, i = -1); // the behavior is undefined
そして、これを次のように簡単に示すことができます。
#define SHOW(X) cout << # X " = " << (X) << endl
int i = 0; /* Yes, it's global! */
void foo(int theI) { SHOW(theI); SHOW(i); }
int main() { foo(i++); }
そうです、iはfoo()が呼び出される前にインクリメントされます。
これはすべて、次の観点から非常に理にかなっています。
class Foo
{
public:
Foo operator++(int) {...} /* Postfix variant */
}
int main() { Foo f; delta( f++ ); }
ここで、 Foo::operator++(int)はdelta()の前に呼び出す必要があります。そして、インクリメント操作はその呼び出し中に完了する必要があります。
私の(おそらく過度に複雑な)例では:
f . bar("A",i) . bar("B",i++) . bar("C",i) . bar("D",i);
object.bar( "B",i++)に使用されるオブジェクトを取得するにはf.bar("A",i)を実行する必要があり、 "C"および"D"についても同様です。
したがって、 i++はbar("B",i++)を呼び出す前にiをインクリメントすることがわかっています (bar("B",...)はiの古い値で呼び出されますが)、したがってiはbar( "C",i)およびbar("D",i)。
j_random_hackerのコメントに戻ります。
j_random_hacker は次
のように書いています。bar() が代わりに int を返すグローバル関数であり、f が int であり、それらの呼び出しが「.」ではなく「^」で接続されている場合、A、C、D のいずれかが可能であると考えるのは正しいですか? 「0」を報告しますか?
この質問は、あなたが思っているよりもずっと複雑です...
質問をコードとして書き直します...
int bar(const char * theString, int theI) { SHOW(...); return i; }
bar("A",i) ^ bar("B",i++) ^ bar("C",i) ^ bar("D",i);
これで、式が 1 つだけになりました。標準によると (セクション 1.9、8 ページ、pdf ページ 20):
注: 演算子は、演算子が実際に結合的または交換的である場合にのみ、通常の数学的規則に従って再グループ化できます。(7) たとえば、次のフラグメントでは: a=a+32760+b+5; 式ステートメントは、a=(((a+32760)+b)+5); とまったく同じように動作します。これらの演算子の結合性と優先順位によるものです。したがって、合計の結果 (a+32760) が次に b に加算され、その結果が 5 に加算され、結果として a に割り当てられる値になります。オーバーフローによって例外が発生し、int で表現できる値の範囲が [-32768,+32767] であるマシンでは、実装はこの式を a=((a+b)+32765) として書き換えることができません。a と b の値がそれぞれ -32754 と -15 の場合、a+b の合計は例外を生成しますが、元の式は生成しません。また、式を a=((a+32765)+b); のように書き換えることもできません。または a=(a+(b+32765)); a と b の値は、それぞれ 4 と -8 または -17 と 12 である可能性があるためです。ただし、オーバーフローが例外を生成せず、オーバーフローの結果が元に戻せるマシンでは、上記の式ステートメントは、同じ結果が発生するため、上記の方法のいずれかで実装によって書き換えることができます。-- 巻末注記】
したがって、優先順位により、式は次のようになると考えるかもしれません。
(
(
( bar("A",i) ^ bar("B",i++)
)
^ bar("C",i)
)
^ bar("D",i)
);
ただし、 (a^b)^c==a^(b^c) はオーバーフローの可能性がないため、任意の順序で書き換えることができます...
しかし、bar() が呼び出されており、仮説的に副作用を伴う可能性があるため、この式は任意の順序で書き直すことはできません。優先ルールは引き続き適用されます。
これにより、 bar() の評価の順序が適切に決定されます。
では、そのi+=1はいつ発生するのでしょうか? bar("B",...)が呼び出される前に、まだ発生する必要があります。( bar("B",....)は古い値で呼び出されますが。)
したがって、bar(C)とbar(D)の前、およびbar(A) の後に確定的に発生しています。
答え: いいえ。コンパイラが標準に準拠している場合、常に「A=0、B=0、C=1、D=1」になります。
しかし、別の問題を考えてみましょう:
i = 0;
int & j = i;
R = i ^ i++ ^ j;
R の値は何ですか?
i+=1がjの前に発生した場合、0^0^1=1 になります。しかし、i+=1が式全体の後に発生した場合、0^0^0=0 になります。
実際、R はゼロです。i+=1は、式が評価されるまで発生しません。
私が考える理由は次のとおりです。
i = 7、i++、i++; // i は 9 になります (有効)
合法です... 次の 3 つの表現があります。
いずれの場合も、 iの値は各式の最後で変更されます。(後続の式が評価される前。)
PS: 考慮してください:
int foo(int theI) { SHOW(theI); SHOW(i); return theI; }
i = 0;
int & j = i;
R = i ^ i++ ^ foo(j);
この場合、foo(j)の前にi+=1を評価する必要があります。 Iは 1 で、Rは 0^0^1=1 です。