6

std :: string(VC11内)のムーブ代入演算子を使用するには何が必要ですか?

割り当て後にvが不要になったため、自動的に使用されることを望みました。この場合、std :: moveは必要ですか?もしそうなら、私は非C++11スワップを使用したほうがよいでしょう。

#include <string>

struct user_t
{
    void set_name(std::string v)
    {
        name_ = v;
        // swap(name_, v);
        // name_ = std::move(v);
    }

    std::string name_;
};

int main()
{
    user_t u;
    u.set_name("Olaf");
    return 0;
}
4

3 に答える 3

11

割り当て後にvが不要になったため、自動的に使用されることを望みました。この場合、std :: moveは必要ですか?

関数から(値によって)返される場合を除いて、左辺値の移動は常に明示的に指定する必要があります。

これにより、誤って何かを移動することを防ぎます。覚えておいてください:動きは破壊的な行為です。あなたはそれがただ起こることを望まない。

name_ = v;また、これが関数の最後の行であるかどうかに基づいてのセマンティクスが変更された場合は奇妙になります。結局のところ、これは完全に合法的なコードです。

name_ = v;
v[0] = 5; //Assuming v has at least one character.

最初の行でコピーを実行し、移動する必要があるのはなぜですか?

もしそうなら、私は非C++11スワップを使用したほうがよいでしょう。

あなたは好きなように行うことができますがstd::move、意図に関してはより明白です。私たちはそれが何を意味し、あなたがそれで何をしているのかを知っています。

于 2012-05-07T21:43:37.657 に答える
7

受け入れられた答えは良い答えです(そして私はそれを賛成しました)。しかし、私はこの質問にもう少し詳しく対処したいと思いました。

私の質問の核心は、なぜムーブ代入演算子を自動的に選択しないのかということです。コンパイラは、割り当て後にvが使用されていないことを知っていますね。それとも、C ++ 11はコンパイラがそれほどスマートである必要はありませんか?

この可能性は、移動セマンティクスの設計中に検討されました。極端な場合、コンパイラーに静的分析を実行させ、可能な場合はいつでもオブジェクトから移動させたい場合があります。

void set_name(std::string v)
{
    name_ = v;  // move from v if it can be proven that some_event is false?
    if (some_event)
       f(v);
}

最終的に、コンパイラにこの種の分析を要求することは非常に注意が必要です。一部のコンパイラは証明を作成できる場合とできない場合があります。したがって、実際には移植性のないコードにつながります。

では、ifステートメントのない単純なケースについてはどうでしょうか。

void foo()
{
    X x;
    Y y(x);
    X x2 = x;  // last use?  move?
}

y.~Y()さて、気づきxがから移動したかどうかを知るのは難しいです。そして一般的に:

void foo()
{
    X x;
    // ...
    // lots of code here
    // ...
    X x2 = x;  // last use?  move?
}

xコンパイラがこれを分析して、へのコピー構築後に本当に使用されなくなったかどうかを知ることは困難ですx2

したがって、元の「移動」提案は、非常に単純で非常に保守的な暗黙の移動のルールを提供しました。

左辺値は、コピーの省略がすでに許可されている場合にのみ暗黙的に移動できます。

例えば:

#include <cassert>

struct X
{
    int i_;
    X() : i_(1) {}
    ~X() {i_ = 0;}
};

struct Y
{
    X* x_;
    Y() : x_(0) {}
    ~Y() {assert(x_ != 0); assert(x_->i_ != 0);}
};

X foo(bool some_test)
{
    Y y;
    X x;
    if (some_test)
    {
        X x2;
        return x2;
    }
    y.x_ = &x;
    return x;
}

int main()
{
    X x = foo(false);
}

ここで、C ++ 98/03の規則により、このプログラムは、コピーの省略が発生するかどうかに応じて、アサートする場合としない場合return xがあります。それが発生した場合、プログラムは正常に実行されます。それが起こらない場合、プログラムはアサートします。

そしてそれは推論されました:RVOが許可されるとき、私たちはすでにの値に関して保証がない領域にいますx。したがって、この余裕を利用してから移動できるはずxです。リスクは小さく見え、利益は大きく見えまし。これは、多くの既存のプログラムが単純な再コンパイルではるかに高速になることを意味するだけでなく、ファクトリ関数から「移動のみ」の型を返すことができることも意味します。これは、リスクに対する非常に大きなメリットです。

標準化プロセスの後半で、私たちは少し貪欲になり、値によるパラメーターを返すときに暗黙の移動が発生する(そして型が戻り型と一致する)とも言いました。ここでもメリットは比較的大きいように見えますが、RVOが合法であった(または合法である)場合ではないため、コードが破損する可能性はわずかに高くなります。しかし、この場合のコードを壊すデモはありません。

したがって、最終的には、コアの質問に対する答えは、移動セマンティクスの元の設計が、既存のコードの解読に関して非常に保守的なルートをとったということです。もしそうでなかったら、それは確かに委員会で撃墜されたでしょう。プロセスの後半に、デザインを少しアグレッシブにするいくつかの変更がありました。しかし、この時までに、核となる提案は、大多数の(しかし全会一致ではない)支持を得て、標準にしっかりと定着していました。

于 2012-05-08T00:46:52.273 に答える
5

あなたの例でset_nameは、文字列を値で取得します。set_nameただし、内部にv は左辺値があります。これらのケースを別々に扱いましょう:

user_t u;
std::string str("Olaf");    // Creates string by copying a char const*.
u.set_name(std::move(str)); // Moves string.

内部set_nameで、の代入演算子を呼び出します。これによりstd::string、不要なコピーが発生します。ただし、の右辺値のオーバーロードもあります。これはoperator=、次の場合により意味があります。

void set_name(std::string v)
{
    name_ = std::move(v);
}

このように、行われる唯一のコピーは文字列の構成(std::string("Olaf"))です。

于 2012-05-07T21:39:07.080 に答える