11

この非常に支持された回答によると、いくつかの要素を消去するセットを反復処理する標準的な方法は次のとおりです。

for (it = mySet.begin(); it != mySet.end(); ) {
    if (conditionToDelete(*it)) {
        mySet.erase(it++);
    }
    else {
        ++it;
    }
}

もちろん、これは C++03 の set erase が反復子を返さない結果です。それ以外の場合は、次のように書くit = mySet.erase(it);ことができます。

itToDelete = it++;
mySet.erase(itToDelete);

この質問は、反復中に要素を削除する方法に関するものではありません。問題は、次の行が明らかに未定義の動作にならない理由です。

mySet.erase(it++);

最初は、ポストインクリメントについて間違った考えを持っていたので、これは UB でなければならないと確信していました。プレインクリメントが残りの評価の前に発生し、ポストインクリメントが後に発生すると考えるのは一般的な (しかし間違った) 方法です。もちろん、これは間違っています。ポストインクリメントとプリインクリメントの両方に、変数をインクリメントするという副作用があります。違いは、それらの式の値です。

とはいえ、私が覚えている限り、C++ 標準 (少なくとも C++03) では、ポストインクリメントの副作用がいつ発生するかを正確に指定していません。では、ポストインクリメント式である関数の引数が関数本体に入る前に副作用を持っているという保証がない限り、これはUB ではないでしょうか? 関数本体内でイテレータが無効化された後に発生する it++ の副作用を禁止しているのは、正確には (標準的に) 何でしょうか?

標準からの引用は大歓迎です。

議論のために、セットの反復子が組み込み型であり、これが実際には演算子 ++ であり、オーバーロードされた演算子関数ではないとします。

4

1 に答える 1

11

すべての関数引数が評価された後にシーケンス ポイントがあるため、これはC++03 では未定義の動作ではありません。

C++03 に最も近く、公開されているドラフト標準はN1804です。私が見つけることができる以前のドラフト標準の公開バージョンはありませんが、シーケンス ポイントに関するウィキペディアの記事ではC++98c++が使用されています。 03を参照し、フレーズはN1804の以下の段落と一致しています。

セクション1.9 プログラムの実行パラグラフ16には次のように書かれています (強調していきます):

関数を呼び出すとき (関数がインラインであるかどうかにかかわらず)、すべての関数引数(存在する場合) の評価の後に、関数本体の式またはステートメントの実行前に行われるシーケンス ポイントがあります。[...]

以降のセクション5.2.2 関数呼び出しの段落8では、次のように述べています。

引数の評価順序は規定されていません。引数式の評価のすべての副作用は、関数に入る前に有効になります。後置式と引数式リストの評価順序は規定されていません。

于 2013-12-20T15:15:08.827 に答える