12

次の式は、未定義の不特定の動作を示すためによく使用されます。

f() + g()

f()g()両方が一部の共有オブジェクトに副作用をもたらす場合、実行の順序が不明であるため、動作は未定義で指定されていません。f()前に評価されるg()か、またはその逆の場合があります。

今、オブジェクトのメンバー関数をチェーンするとどうなるのだろうと思っていました。クラスのインスタンスがあり、そのインスタンスが呼び出されobj、2つのメンバー関数がfoo()あり、bar()どちらもオブジェクトを変更するとします。これらの関数の実行順序は可換ではありません。前後に呼び出すことの効果は、逆に呼び出すことと同じ効果ではありません。どちらのメソッドもへの参照を返す*thisため、次のようにチェーンできます。

obj.foo().bar()

しかし、これは不特定の振る舞いですか?この表現と私が投稿の上部に示した表現を区別するものは、標準には見つかりません(確かにスキャンスルーするだけです)。両方の関数呼び出しは完全式の部分式であるため、実行の順序は指定されていません。ただし、変更するオブジェクトがわかるように、最初に評価するfoo() 必要があります。bar()

明らかな何かが欠けているかもしれませんが、シーケンスポイントがどこに作成されているかわかりません。

4

3 に答える 3

10
f() + g()

ここでは、各オペランドが評価される(つまり、各関数が呼び出される)順序が指定されていないため、動作は指定されていません(未定義ではありません) 。

 obj.foo().bar();

これはC++で明確に定義されています。

C ++ ISO規格の関連セクション§1.9.17には、次のように書かれています。

関数を呼び出すとき(関数がインラインであるかどうかに関係なく)、 すべての関数の引数(存在する場合) の評価後、関数本体の式またはステートメントの実行前に実行されるシーケンスポイントがあります。戻り値のコピー後、関数外の式の実行前にシーケンスポイントがあります。

これらのトピックでは、同様のケースが非常に詳細に説明されています。

于 2011-04-02T13:13:27.587 に答える
5

f()とg()の両方が一部の共有オブジェクトに副作用をもたらす場合、実行の順序が不明であるため、動作は定義されていません。

本当じゃない。関数の呼び出しはインターリーブせず、関数に入る前と関数を出る前にシーケンスポイントがあります。の副作用にg対応するすべての副作用はf、少なくとも1つのシーケンスポイントで区切られています。動作は未定義ではありません。

結果として、関数の実行順序はf決定gされませんが、一方の関数が実行されると、その関数の評価のみが実行され、もう一方の関数は「待機する必要があります」。さまざまな観察可能な結果が可能ですが、これは未定義の動作が発生したことを意味するものではありません。

今、オブジェクトのメンバー関数をチェーンするとどうなるのだろうと思っていました。

持っている場合は、最初に関数を呼び出すオブジェクトを知るためobj.foo().bar()に評価する必要があります。つまり、戻って値を生成するのを待つ必要があります。ただし、これは必ずしも評価によって開始されたすべての副作用が終了したことを意味するわけではありません。式を評価した後、これらの副作用が完全であると見なされるためのシーケンスポイントが必要です。から戻る前と呼び出す前にシーケンスポイントがあるため、との式をそれぞれ評価することによって開始される副作用を実行するための順序が効果的に決定されます。obj.foo()barobj.foo()obj.foo()obj.foo()bar()foobar

もう少し説明すると、この例でfoo前に呼び出された理由は、次の関数が呼び出される前に最初にインクリメントされる理由と同じです。bari++f

int i = 0;
void f() {
  std::cout << i << std::endl;
}

typedef void (*fptype)();
fptype fs[] = { f };

int main() {
  fs[i++]();
}

ここで尋ねる質問は次のとおりです。このプログラムは印刷されますか01それともその動作は未定義または未指定ですか?答えは、式はfs[i++]必然的に関数呼び出しの前に最初に評価される必要があり、入力する前にシーケンスポイントがあるため、 insidefの値はです。if1

ケースを説明するために暗黙のオブジェクトパラメータのスコープをシーケンスポイントに拡張する必要はないと思います。また、このケースを説明するために拡張することはできません(これは定義された動作であると思います)。


C ++ 0xドラフト(シーケンスポイントはもうありません)には、これについてより明確な表現があります(私のものを強調してください)

関数を呼び出すとき(関数がインラインであるかどうかに関係なく)、引数式、または呼び出された関数を指定する後置式に関連付けられたすべての値の計算と副作用は、本体のすべての式またはステートメントの実行前にシーケンスされます。関数と呼ばれます。

于 2011-04-02T13:14:48.583 に答える
1

Foo()はbar()の前に実行されます。最初にFoo()を決定する必要があります。そうしないと、bar()がどのタイプに属するかがわかりません(bar()がどのタイプのクラスに属しているかさえわかりません。考えれば、より直感的に理解できるかもしれません。 fooがこれの代わりにobjの新しいインスタンスを返した場合、またはfooがbar()メソッドも定義されているまったく異なるクラスのインスタンスを返した場合は、これを行う必要があります。

fooとbarのブレークポイントを使用して、どちらが最初にヒットするかを確認することで、これを自分でテストできます。

于 2011-04-02T13:06:21.157 に答える