85

このトピックを次のトピックの続編と考えてください。

前の記事
未定義の動作とシーケンスポイント

この面白く複雑な表現をもう一度見てみましょう(斜体のフレーズは上記のトピック* smile *から取られています):

i += ++i;

これは未定義動作を引き起こすと言います。これを言うとき、私たちは暗黙のうちにタイプが組み込みタイプの1つであると仮定していると思います。i

タイプiがユーザー定義タイプの場合はどうなりますか?そのタイプは、この投稿の後半で定義されていると言いますIndex(以下を参照)。それでも未定義動作を呼び出しますか?

はいの場合、なぜですか?それは書くことと同等ではありませんi.operator+=(i.operator++());か、あるいは構文的に単純 i.add(i.inc());ですか?または、それらも未定義動作を呼び出しますか?

いいえの場合、なぜですか?結局のところ、オブジェクトは連続するシーケンスポイント間で2回i変更されます。経験則を思い出してください。式は、連続する「シーケンスポイント間でオブジェクトの値を1回だけ変更できます。また、 が式の場合、undefined-behaviorを呼び出す必要があります。その場合、同等のもので あり、undefined-behaviorを呼び出す必要があります。真実ではないようです!(私が理解している限り)i += ++ii.operator+=(i.operator++());i.add(i.inc());

または、そもそも表現i += ++iではないですか?もしそうなら、それは何ですか、そして表現の定義は何ですか?

それが式であると同時に、その動作明確に定義されている場合、式に関連付けられているシーケンスポイントの数は、式に含まれるオペランドのタイプに何らかの形で依存することを意味します。私は(部分的にでも)正しいですか?


ちなみに、この表現はどうですか?

//Consider two cases:
//1. If a is an array of a built-in type
//2. If a is user-defined type which overloads the subscript operator!

a[++i] = i; //Taken from the previous topic. But here type of `i` is Index.

応答でもこれを考慮する必要があります(その動作を確実に知っている場合)。:-)


++++++i;

C ++ 03で明確に定義されていますか?結局のところ、これはこれです、

((i.operator++()).operator++()).operator++();

class Index
{
    int state;

    public:
        Index(int s) : state(s) {}
        Index& operator++()
        {
            state++;
            return *this;
        }
        Index& operator+=(const Index & index)
        {
            state+= index.state;
            return *this;
        }
        operator int()
        {
            return state;
        }
        Index & add(const Index & index)
        {
            state += index.state;
            return *this;
        }
        Index & inc()
        {
            state++;
            return *this;
        }
};
4

5 に答える 5

48

コードのように見えます

i.operator+=(i.operator ++());

シーケンスポイントに関しては完全に正常に機能します。C ++ ISO標準のセクション1.9.17は、シーケンスポイントと関数評価について次のように述べています。

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

これは、たとえば、i.operator ++()パラメータとしてがoperator +=評価後にシーケンスポイントを持っていることを示します。つまり、オーバーロードされた演算子は関数であるため、通常の順序付けルールが適用されます。

ちなみに、すばらしい質問です。私は、あなたが私がすでに知っていると思っていた(そして私が知っていると思った)言語のすべてのニュアンスを理解するように強制している方法が本当に好きです。:-)

于 2011-01-09T08:47:32.700 に答える
12

http://www.eelis.net/C++/analogliterals.xhtml アナログ リテラルが頭に浮かぶ

  unsigned int c = ( o-----o
                     |     !
                     !     !
                     !     !
                     o-----o ).area;

  assert( c == (I-----I) * (I-------I) );

  assert( ( o-----o
            |     !
            !     !
            !     !
            !     !
            o-----o ).area == ( o---------o
                                |         !
                                !         !
                                o---------o ).area );
于 2011-01-09T16:59:48.533 に答える
11

他の人が言ったようi += ++iに、関数を呼び出しているため、この例はユーザー定義型で機能し、関数はシーケンス ポイントを構成します。

一方、それが基本的な配列タイプであるか、ユーザー定義のタイプでさえあるとa[++i] = i仮定すると、それほど幸運ではありません。ここでの問題は、含む式のどの部分が最初に評価されるaかわからないことです。iそれが++i評価され、operator[]そこにあるオブジェクトを取得するために (または未加工のバージョン) に渡され、次に の値がそれiに渡されます (iインクリメントされた後)。一方、おそらく後者の側が最初に評価され、後で割り当てられるように保存されてから、++i部分が評価されます。

于 2011-01-09T09:35:22.560 に答える
8

私はそれが明確に定義されていると思います:

C ++ドラフト標準(n1905)§1.9/ 16から:

「戻り値のコピー後、関数外の式の実行前にもシーケンスポイントがあります13)。C++のいくつかのコンテキストでは、対応する関数呼び出し構文が変換ユニットに表示されていなくても、関数呼び出しの評価が行われます。 [:新しい式の評価により、1つ以上の割り当て関数とコンストラクター関数が呼び出されます。5.3.4を参照してください。別の例では、関数呼び出し構文が表示されないコンテキストで変換関数(12.3.2)の呼び出しが発生する可能性があります。— end例]関数の入口と出口のシーケンスポイント(上記のとおり)は、式の構文に関係なく、評価された関数呼び出しの機能です。関数を呼び出す可能性があります。「」

太字の部分に注意してください。これは、インクリメント関数呼び出し(i.operator ++())の後、複合代入呼び出し(i.operator+=)の前に実際にシーケンスポイントがあることを意味します。

于 2011-01-09T08:48:46.830 に答える
6

大丈夫。以前の回答を見た後、私は自分自身の質問、特にノアだけが答えようとしたこの部分について再考しましたが、私は彼に完全に納得していません.

a[++i] = i;

ケース 1:

Ifaは組み込み型の配列です。なら、ノアの言ったことは正しい。あれは、

a[++i] = i は、a が基本的な配列型であると仮定すると、それほど幸運ではありません。またはユーザー定義のもの. ここでの問題は、 i を含む式のどの部分が最初に評価されるかわからないことです。

そのa[++i]=iため、undefined-behavior を呼び出すか、結果が未規定です。それが何であれ、それは明確に定義されていません!

PS: 上記の引用では、取り消し線もちろん私のです。

ケース 2:

aが をオーバーロードするユーザー定義型のオブジェクトである場合operator[]も、2 つのケースがあります。

  1. オーバーロードoperator[]された関数の戻り値の型が組み込み型の場合、再びa[++i]=iundefined-behavior が呼び出されるか、結果が未指定になります。
  2. しかし、オーバーロードされた関数の戻り値の型がoperator[]ユーザー定義型である場合、 の動作はa[++i] = i明確に定義されています (私が理解している限り) 。つまり、割り当ては の返されたオブジェクトで呼び出されます。これは非常に明確に定義されているように見えます。なぜなら、返されるまでには既に評価されており、返されたオブジェクトはの更新された値を引数として渡す関数を呼び出します。これら 2 つの呼び出しの間にシーケンス ポイントがあることに注意してください。また、この構文により、これら 2 つの呼び出し間で競合が発生しないことが保証されます。a[++i]=ia.operator[](++i).operator=(i);a[++i].operator=(i);operator=a[++i]a[++i]++ioperator=ioperator[]最初に呼び出され、続いて++i渡された引数も最初に評価されます。

someInstance.Fun(++k).Gun(10).Sun(k).Tun();これは、連続する各関数呼び出しが何らかのユーザー定義型のオブジェクトを返すものと考えてください。eat(++k);drink(10);sleep(k)どちらの状況でも、各関数呼び出しの後にシーケンス ポイントが存在するためです。

私が間違っている場合は、私を修正してください。:-)

于 2011-01-09T10:39:04.703 に答える