7

次のコードをテストしました。

#include <iostream>
using namespace std;
class foo{
public:
    foo()       {cout<<"foo()"<<endl;}
    ~foo()      {cout<<"~foo()"<<endl;}
};

int main()
{
    foo f;
    move(f);
    cout<<"statement \"move(f);\" done."<<endl;
    return 0;
}

出力は次のとおりです。

foo()
statement "move(f);" done.
~foo()

しかし、私は期待しました

foo()
~foo()
statement "move(f);" done.

関数 move のソース コードによると、次のようになります。

  template<typename _Tp>
    constexpr typename std::remove_reference<_Tp>::type&&
    move(_Tp&& __t) noexcept
    { return static_cast<typename std::remove_reference<_Tp>::type&&>(__t); }

返されたオブジェクトは正しい値ですが、なぜすぐに破棄されないのでしょうか?



-------------------------------------------------- ---------------
右辺値と右辺値参照を混同しただけだと思います。
コードを変更しました:

#include <iostream>

template<typename _Tp>
constexpr typename /**/std::remove_reference<_Tp>::type /* no && */
/**/ mymove /**/ (_Tp&& __t) noexcept
{ return static_cast<typename std::remove_reference<_Tp>::type&&>(__t); }

using namespace std;
class foo{
public:
    foo()       {cout<<"foo() at "<<this<<endl;} /* use address to trace different objects */
    ~foo()      {cout<<"~foo() at "<<this<<endl;} /* use address to trace different objects */
};

int main()
{
    foo f;
    mymove(f);
    cout<<"statement \"mymove(f);\" done."<<endl;
    return 0;
}

そして今、私は期待していたものを手に入れました:

foo() at 0x22fefe
~foo() at 0x22feff
statement "mymove(f);" done.
~foo() at 0x22fefe
4

6 に答える 6

15

オブジェクトから移動しても、その有効期間は変更されず、現在の値のみが変更されます。オブジェクトfooは、出力後の から戻ると破棄されmainます。

さらに、std::moveオブジェクトから移動しません。参照先がオブジェクトである右辺値参照を返すだけで、オブジェクトからの移動が可能になります。

于 2013-03-27T16:20:03.447 に答える
6

オブジェクトは、スコープ外に出ると破棄されます。オブジェクトから移動しても、それは変わりません。移動コンストラクターまたは移動代入演算子の動作に応じて、移動後のオブジェクトの状態は異なる可能性がありますが、まだ破棄されていないため、実際のルールでは、オブジェクトからの移動ではオブジェクトを次の状態のままにしておく必要があります。破壊することができます。

それ以上は、@ R.MartinhoFernandes が指摘しているように、std::move何もしません。実行する必要があることを実行するのは、オブジェクトのムーブ コンストラクターまたはムーブ代入演算子であり、呼び出しには適用されませんstd::move。移動元オブジェクトを使用して新しいオブジェクトを構築する場合 (移動コンストラクター)、または既存のオブジェクトに割り当てられる場合 (移動代入演算子) に適用されます。このような:

foo f;
foo f1(f);            // applies foo's copy constructor
foo f2(std::move(f)); // applies foo's move constructor
foo f3, f4;
f3 = f;               // applies foo's copy assignment operator
f4 = std::move(f1);   // applies foo's move assignment operator
于 2013-03-27T16:20:45.187 に答える
2

Astd::moveはオブジェクトのライフタイムを変更しません。大まかに言えばstatic_cast、非 const 左辺値を非 const 右辺値参照にキャストするのは a にすぎません。

これの有用性は、オーバーロードの解決です。実際、一部の関数は const 左辺値参照 (コピー コンストラクターなど) によってパラメーターを取得し、他の関数は非 const 右辺値参照 (ムーブ コンストラクターなど) によって取得します。渡されたオブジェクトが一時的なものである場合、コンパイラは 2 番目のオーバーロードを呼び出します。アイデアは、関数が呼び出された直後に一時的なものを使用できなくなる (そして破棄される) ということです。したがって、2 番目のオーバーロードは、一時リソースに対処する代わりに、そのリソースの所有権を取得する可能性があります。

ただし、コンパイラは非一時オブジェクト (または、より正確には左辺値) に対しては実行しません。その理由は、渡されたオブジェクトの名前がスコープ内に残っているため、(コードが示すように) まだ使用できる可能性があるためです。そのため、その内部リソースが引き続き必要になる可能性があり、それらが他のオブジェクトに移動された場合は問題になります。それでも、 を使用して 2 番目のオーバーロードを呼び出すことができるようにコンパイラに指示できますstd::move。引数を右辺値参照にキャストし、オーバーロードの解決によって、2 番目のオーバーロードが呼び出されます。

以下のわずかに変更されたコードは、この点を示しています。

#include <iostream>

using namespace std;

class foo{
public:
    foo()  { cout << "foo()"  << endl; }
    ~foo() { cout << "~foo()" << endl; }
};

void g(const foo&) { cout << "lref" << endl; }
void g(foo&&)      { cout << "rref" << endl; }

int main()
{
    foo f;
    g(f);

    g(move(f));

    // f is still in scope and can be referenced.
    // For instance, we can call g(f) again.
    // Imagine what would happen if f had been destroyed as the question's author
    // originally though?

    g(static_cast<foo&&>(f)); // This is equivalent to the previous line

    cout<<"statement \"move(f);\" done."<<endl;
    return 0;
}

出力は

foo()
lref
rref
rref
statement "move(f);" done.
~foo()

更新:(質問が使用するように変更された後mymove

新しいコードは、最初に言ったこととまったく同じではないことに注意してください。実際~foo()、1 つではなく 2 つの呼び出しを報告します。

foo表示されたアドレスから、タイプ の元のオブジェクト、つまりfが最後に破棄されていることがわかります。元のコードとまったく同じです。多くの人が指摘しているように、fはスコープの最後 ( function の本体) でのみ破棄されmainます。これは今でもそうです。

のコピーである別のオブジェクトを破棄する~foo()直前に報告されたへの余分な呼び出し。レポート コピー コンストラクターを に追加すると、次のようになります。statement "mymove(f);" done.ffoo

foo(const foo& orig) { cout << "copy foo from " << &orig << " to " << this << endl;}      

次に、次のような出力が得られます。

foo() at 0xa74203de
copy foo from 0xa74203de to 0xa74203df
~foo() at 0xa74203df
statement "move(f);" done.
~foo() at 0xa74203de

を呼び出すと、別のオブジェクトmymoveにコピーするためのコピー コンストラクターが呼び出されると推測できます。次に、この新しく作成されたオブジェクトは、実行が表示される行に到達する前に破棄されますffoostatement "move(f);" done.

当然の疑問は、このコピーがどこから来たのかということです。さて、戻り値の型に注目してmymoveください:

constexpr typename /**/std::remove_reference<_Tp>::type /* no && */`

この例では、わかりやすくするために単純化した後、これは に要約されfooます。つまり、 by 値をmymove返します。fooしたがって、一時オブジェクトを作成するためにコピーが作成されます。前に述べたように、テンポラリーは、それを作成する式の評価が終了した直後に破棄されます (この規則には例外がありますが、このコードには適用されません)。それは への余分な呼び出しを説明してい~foo()ます。

于 2013-03-27T16:27:01.733 に答える
1

一般的なケースでは、移動は別の翻訳単位で発生する可能性があるためです。あなたの例では、オブジェクトは移動さえされておらず、可動としてマークされているだけです。これはstd::move、オブジェクトが移動されたかどうかを呼び出し元が知らないことを意味します。彼が知っているのは、オブジェクトが存在し、そのオブジェクトのスコープ/ライフタイムの終わりにデストラクタを呼び出さなければならないことだけです。オブジェクトを可動std::moveとしてマークするだけで、移動操作を実行したり、さらに移動できる移動コピーを作成したりしません。

検討:

// translation unit 1
void f( std::vector< int >&& v )
{
  if( v.size() > 8 ) {
    // move it
  }
  else {
    // copy it as it's just a few entries
  }
}

// translation unit 2

void f( std::vector< int >&& );
std::vector< int > g();

int main()
{
  // v is created here
  std::vector< int > v = g();

  // it is maybe moved here
  f( std::move( v ) );

  // here, v still exists as an object
  // when main() ends, it will be destroyed
}

上記の例では、変換ユニット 2 は、? の後にデストラクタを呼び出すかどうかをどのように決定しstd::moveますか?

于 2013-03-27T16:09:15.293 に答える
1

あなたは名前に混乱しています-std::move実際には何も動かしません. 左辺値参照を右辺値参照に変換 (キャスト) するだけで、他の誰かに何かを移動させるために使用されます。

が役立つのは、左辺値または右辺値参照のいずれかを取るオーバーロードされた関数がある場合std::moveです。このような関数を単純な変数で呼び出す場合、オーバーロードの解決は、左辺値参照を使用してバージョンを呼び出すことを意味します。明示的な呼び出しを追加してstd::move、代わりに右辺値参照バージョンを呼び出すことができます。右辺値参照を受け取る関数内を除いて、移動は必要ありません。

これが move と呼ばれる理由は、2 つのコンストラクターがあり、そのうちの 1 つは左辺値参照 (一般にコピー コンストラクターと呼ばれます) を受け取り、もう 1 つは右辺値参照 (一般に移動コンストラクターと呼ばれます) を取る一般的な使用法です。この場合、明示的な呼び出しを追加するstd::moveことは、コピー コンストラクターの代わりに移動コンストラクターを呼び出すことを意味します。

より一般的なケースでは、左辺値バージョンがオブジェクトのコピーを作成し、右辺値バージョンがオブジェクトを移動する左辺値/右辺値参照を取る関数をオーバーロードするのが一般的です (ソース オブジェクトを暗黙的に変更して、使用するメモリを引き継ぐ)。 .

于 2013-03-27T16:25:06.780 に答える
0

次のようなコードを考えてみましょう:

void bar(Foo& a) {
  if (a.noLongerneeded())
    lastUnneeded = std::move(a);
}

この場合、 の呼び出し元はbar、関数が渡されたオブジェクトのデストラクタを呼び出すことになる場合があることを知ることができません。そのオブジェクトに責任があると感じ、後でそのデストラクタを呼び出すようにします。

したがって、ルールはmove、有効なオブジェクトを別のまだ有効なオブジェクトに変える可能性があるということです。それでも有効とは、そのオブジェクトのメソッドまたはデストラクタを呼び出しても、明確に定義された結果が得られることを意味します。厳密に言えば、moveそれ自体では、そのような参照の受信者に、オブジェクトを変更することが理にかなっている場合に変更する可能性があることを伝える以外には何もしません。したがって、実際の移動を行うのは、ムーブ コンストラクターやムーブ代入演算子などの受信者です。これらの操作は通常、オブジェクトをまったく変更しないか、一部のポインタをnullptrに設定するか、一部の長さをゼロに設定するか、またはそのようなものにします。ただし、そのタスクはオブジェクトの所有者に任されているため、デストラクタを呼び出すことはありません。

于 2013-03-27T16:22:05.933 に答える