10

C ++ 0x移動セマンティクスを使用してオブジェクトが移動された後の、オブジェクトの状態について混乱しています。私の理解では、オブジェクトが移動されても、それはまだ有効なオブジェクトですが、その内部状態が変更されているため、デストラクタが呼び出されたときにリソースの割り当てが解除されません。

しかし、私の理解が正しければ、移動したオブジェクトのデストラクタを呼び出す必要があります。

しかし、私が簡単なテストを実行するとき、それは起こりません:

struct Foo
{
    Foo()  
    {
        s = new char[100]; 
        cout << "Constructor called!" << endl;  
    }

    Foo(Foo&& f) 
    {
        s = f.s;
        f.s = 0;
    }

    ~Foo() 
    { 
        cout << "Destructor called!" << endl;   
        delete[] s; // okay if s is NULL
    }

    void dosomething() { cout << "Doing something..." << endl; }

    char* s;
};

void work(Foo&& f2)
{
    f2.dosomething();
}

int main()
{
    Foo f1;
    work(std::move(f1));
}

この出力:

Constructor called!
Doing something...
Destructor called!

デストラクタが呼び出されるのは1回だけであることに注意してください。これは、ここでの私の理解がずれていることを示しています。デストラクタが2回呼び出されなかったのはなぜですか?何が起こったのかについての私の解釈は次のとおりです。

  1. Foo f1構築されます。
  2. Foo f1に渡されwork、右辺値を取りますf2
  3. のmoveコンストラクターFooが呼び出され、すべてのリソースがに移動f1f2ます。
  4. f2これで、のデストラクタが呼び出され、すべてのリソースが解放されます。
  5. これで、のデストラクタが呼び出されますf1が、すべてのリソースがに転送されたため、実際には何も実行されませんf2。それでも、デストラクタは呼び出されます。

ただし、呼び出されるデストラクタは1つだけなので、ステップ4またはステップ5は実行されません。デストラクタからバックトレースを実行して、どこから呼び出されているかを確認しました。これは、手順5から呼び出されています。では、なぜf2デストラクタも呼び出されないのでしょうか。

編集:さて、私はこれを変更して、実際にリソースを管理しているようにしました。(内部メモリバッファ。)それでも、デストラクタが1回だけ呼び出される場合と同じ動作が発生します。

4

2 に答える 2

9

編集 (新しくて正解)
申し訳ありませんが、コードを詳しく見ると、答えははるかに単純なようです。moveコンストラクターを呼び出すことはありません。実際にオブジェクトを移動することはありません。関数に右辺値参照を渡すだけです。この参照は、元のオブジェクトを指すworkメンバー関数を呼び出します。

後世のために保存された元の回答

Foo f3(std::move(f2));実際に移動を行うには、内部のようなものが必要ですworkf3次に、から移動して作成された新しいオブジェクトであるメンバー関数を呼び出すことができます。f

私が見る限り、移動セマンティクスはまったく取得されません。あなたはただ古いコピーの省略を見ているだけです。

移動を行うには、std::moveから返されるような右辺値参照を使用する必要があります(または、具体的には、コンストラクターに渡される引数は名前のない/一時的なものである必要がありstd::moveます)。それ以外の場合は、昔ながらの左辺値参照として扱われ、コピーが発生するはずですが、通常どおり、コンパイラーはそれを最適化して、1つのオブジェクトを作成し、1つのオブジェクトを破棄することができます。

とにかく、移動セマンティクスを使用しても、コンパイラが同じことを行うべきではない理由はありません。コピーを最適化したのと同じように、移動を最適化するだけです。移動は安価ですが、オブジェクトを作成してから別の場所に移動し、最初のオブジェクトでデストラクタを呼び出すよりも、必要な場所にオブジェクトを作成する方がさらに安価です。

また、比較的古いコンパイラを使用していることにも注意してください。以前のバージョンの仕様では、これらの「ゾンビオブジェクト」で何が起こるかについて非常に不明確でした。したがって、GCC4.3がデストラクタを呼び出さない可能性があります。デストラクタを明示的に呼び出す必要があるのは、最後のリビジョン、またはその前のリビジョンだけだと思います

于 2010-11-02T21:07:25.150 に答える
2

コンパイラは、不要な構築/破棄を最適化して、事実上1つのオブジェクトのみを存在させる可能性があることに注意してください。これは、右辺値参照(まさにこの目的のために発明されたもの)に特に当てはまります。

何が起こるかについてのあなたの解釈は間違っていると思います。移動コンストラクターは呼び出されません。値が一時的でない場合、右辺値参照は通常の参照として動作します。

たぶん、この記事は右辺値参照のセマンティクスに関するより多くの情報をもたらすでしょう。

于 2010-11-02T21:05:38.443 に答える