3

重複の可能性:
未定義の動作とシーケンスポイント

接尾辞演算子をオーバーロードするときのアクションの順序を理解するのに問題があります。以下の2つの小さな例を見てみましょう。

int i = 0;
std::cout << std::endl << "i: " << i;
i = ++i;
std::cout << std::endl << "i: " << i;
i = i++;
std::cout << std::endl << "i: " << i;

MyClass myObject;
std::cout << std::endl << "myObject: " << myObject.getMyValue();
myObject = ++myObject;
std::cout << std::endl << "myObject: " << myObject.getMyValue();
myObject = myObject++;
std::cout << std::endl << "myObject: " << myObject.getMyValue();

2つの非常に異なる動作が発生します。出力は次のとおりです。

i: 0
i: 1
i: 2
myObject: 0
myObject: 1
myObject: 1

異なる振る舞いですね。これが私のオーバーロードされた演算子メソッドの概要です。

MyClass & MyClass::operator++ ()
{
    ++myValue;
    return *this;
}

MyClass MyClass::operator++ (int postfixFlag)
{
    MyClass myTemp(*this);
    ++myValue;
    return myTemp;
}

大丈夫。プレフィックスは理にかなっています。必要なものは何でもインクリメントしてから、割り当ての場合に変更された同じオブジェクトを返します。しかし、接尾辞は私をつまずかせるものです。割り当ててからインクリメントすることになっています。ここでは、自己割り当てを行っています。したがって、組み込みの整数型を使用すると、それは理にかなっています。iの値をそれ自体に割り当ててから、iインクリメントします。けっこうだ。MyClassしかし、intのレクリエーションだとしましょう。0から始まり、プレフィックスがインクリメントされて1になります。次に、キーライン。 myObject = myObject++。それはと同じことですmyObject = myObject.operator++(int postfixFlag)。呼ばれます。 myTemp値1で初期化されます。2にインクリメントされます。次に、tempを返します。別のオブジェクトに割り当てる場合は、これで機能します。しかし、ここでは自己割り当てしているので、2にインクリメントした後myObjectは初期値で初期化された戻り値の一時オブジェクトと等しく設定され、1に戻ります。それは理にかなっている。しかし、それは根本的に異なる振る舞いです。

どうすれば回避できますか?intはどのようにそれを行いますか?このメソッドは一般的にどのように記述されていますか?これに関連するC++の動作と設計についてコメントはありますか?等本やオンラインの例は常に上記の方法の変形を使用しているように見えるので、私は今少し困惑しています。

読んでくれてありがとう、そしてどんな入力でも大歓迎です!

4

1 に答える 1

5

他の人が言っているように、intでは動作は定義されていません。しかし、MyClassで2にならない理由を説明しようと思いました。

秘訣は、postfixバージョンで次の3つのステップを実行していることです。

  1. (with )のコピーを作成しthisます。myTempmyValue == 1
  2. インクリメントthis->myValue(そうmyTemp.myValue == 1; this->myValue == 2)。
  3. 戻るmyTemp(とmyValue == 1)。

したがって、変更thisしますが、を呼び出すコードmyObject++は二度と表示されませんthis古い のコピーである、返された値のみを確認しますmyObject

operator++のコードは問題ありません。問題は、それをどのように使用するかです。プリインクリメントまたはポストインクリメントの結果を同じ変数に書き戻すべきではありません(動作は定義されていません)。より有益なコードを次に示します。

int i = 0;
std::cout << "i: " << i << std::endl;
int j = ++i;
std::cout << "i: " << i << ", j: " << j << std::endl;
int k = i++;
std::cout << "i: " << i << ", k: " << k << std::endl;

MyClass myObject;
std::cout << "myObject: " << myObject.getMyValue() << std::endl;
MyClass myObject1 = ++myObject;
std::cout << "myObject: " << myObject.getMyValue()
    << ", myObject1: " << myObject1.getMyValue() << std::endl;
MyClass myObject2 = myObject++;
std::cout << "myObject: " << myObject.getMyValue()
    << ", myObject2: " << myObject2.getMyValue() << std::endl;

これは印刷します:

i: 0
i: 1, j: 1
i: 2, k: 1
myObject: 0
myObject: 1, myObject1: 1
myObject: 2, myObject2: 1

コードを変更して、自分自身に割り当てるのではなく、毎回新しい変数に割り当てるようにしました。intとの両方のMyClass場合で、メイン変数(i/ myObject)は両方の時間でインクリメントされることに注意してください。ただし、インクリメント前の場合は、新しい変数(j/ myObject1)が新しい値を取り、インクリメント後の場合は、新しい変数(k/ myObject2)が古い値を取ります。

編集:「intはどのようにそれを行うのですか?」という質問の別の部分に答えるだけです。intこの質問は、「クラス内でインクリメント前とインクリメント後のコードはどのように見えるのか、どうすれば同じものにすることができるのか」という意味だと思います。int答えは、「クラス」はないということです。intはC++の特別な組み込み型であり、コンパイラはそれを特別に扱います。これらの型は通常のC++コードでは定義されておらず、コンパイラにハードコードされています。

:これを自分で試してみたい人のためMyClassに、質問に含まれていなかったコードを次に示します。

class MyClass
{
private:
    int myValue;
public:
    MyClass() : myValue(0) {}
    int getMyValue() { return myValue; }
    MyClass& operator++();
    MyClass operator++(int postfixFlag);
};
于 2011-06-29T04:51:40.183 に答える