代入演算子と `if(this!=&rhs)`を移動します


125

クラスの代入演算子では、通常、割り当てられているオブジェクトが呼び出し側オブジェクトであるかどうかを確認して、物事を台無しにしないようにする必要があります。

Class& Class::operator=(const Class& rhs) {
    if (this != &rhs) {
        // do the assignment
    }

    return *this;
}

移動代入演算子にも同じものが必要ですか?this == &rhs真実となる状況はありますか?

? Class::operator=(Class&& rhs) {
    ?
}

12
質問されているQとは無関係であり、このQをタイムラインで読んだ新しいユーザーが(セスがすでに知っていることを知っているため)間違った考えを知らないようにするために、コピーとスワップは、コピー割り当て演算子を実装する正しい方法です。自己割り当てなどをチェックする必要はありません。
Alok Save

5
@VaughnCato: A a; a = std::move(a);
Xeo

11
@VaughnCato使用std::moveは正常です。次に、エイリアスを考慮に入れます。コールスタックの奥深くにあり、への参照が1つと... Tへの別の参照Tがある場合、ここでIDを確認しますか?同じ引数を2回渡せないことを文書化すると、これらの2つの参照がエイリアスにならないことが静的に証明される最初の呼び出しを見つけますか?それとも、自己割り当てをそのまま機能させますか?
Luc Danton、

2
@LucDanton代入演算子でのアサーションを好みます。std :: moveが、右辺値の自己割り当てで終わる可能性がある方法で使用された場合、私はそれを修正すべきバグと考えます。
Vaughn Cato

4
@VaughnCato自己スワップが正常に行われる場所の1つは、std::sortまたはのどちらかstd::shuffleです。最初にチェックせずに配列のth i番目とjth番目の要素を交換するときはいつでもi != j。(std::swap移動割り当ての観点から実装されています。)
Quuxplusone 2014

回答:


143

うわー、ここで片付けることがたくさんあります...

まず、コピーとスワップは、常にコピー割り当てを実装する正しい方法ではありません。ほぼ間違いなくの場合dumb_array、これは次善のソリューションです。

使用コピーとスワップのためにあるdumb_array最下層に最大限の機能を備えた最も高価な操作を置くの典型的な例です。これは、最大限の機能を必要とし、パフォーマンスのペナルティを支払う意思があるクライアントに最適です。彼らはまさに彼らが望むものを手に入れます。

しかし、完全な機能を必要とせず、代わりに最高のパフォーマンスを求めているクライアントにとっては悲惨です。彼らにとってdumb_array、それは遅すぎるので彼らが書き直さなければならないもう一つのソフトウェアです。持っていたdumb_array異なる設計されて、それがいずれかのクライアントへの妥協なしで両方のクライアントを満足している可能性があります。

両方のクライアントを満足させるための鍵は、最下位レベルで最速のオペレーションを構築し、その上にAPIを追加して、より多くの費用でより完全な機能を実現することです。つまり、強力な例外保証が必要です。罰金を払ってください。必要ないの?これはより速い解決策です。

具体的に見てみましょう:以下は、高速で基本的な例外保証の代入代入演算子ですdumb_array

dumb_array& operator=(const dumb_array& other)
{
    if (this != &other)
    {
        if (mSize != other.mSize)
        {
            delete [] mArray;
            mArray = nullptr;
            mArray = other.mSize ? new int[other.mSize] : nullptr;
            mSize = other.mSize;
        }
        std::copy(other.mArray, other.mArray + mSize, mArray);
    }
    return *this;
}

説明:

最新のハードウェアで実行できるより高価なことの1つは、ヒープにアクセスすることです。ヒープへの移動を回避するためにできることはすべて、時間と労力を費やすことです。のクライアントはdumb_array、しばしば同じサイズの配列を割り当てたいと思うかもしれません。そして、彼らがそうするとき、あなたがする必要があるのはmemcpy(の下に隠されているstd::copy)です。同じサイズの新しい配列を割り当ててから、同じサイズの古い配列の割り当てを解除する必要はありません。

強力な例外安全を実際に必要とするクライアントのために:

template <class C>
C&
strong_assign(C& lhs, C rhs)
{
    swap(lhs, rhs);
    return lhs;
}

または、C ++ 11の移動割り当てを利用したい場合は、次のようにする必要があります。

template <class C>
C&
strong_assign(C& lhs, C rhs)
{
    lhs = std::move(rhs);
    return lhs;
}

dumb_arrayクライアントが速度を重視する場合は、を呼び出す必要がありoperator=ます。強力な例外安全性が必要な場合は、さまざまなオブジェクトで機能し、一度だけ実装する必要がある、呼び出し可能な汎用アルゴリズムがあります。

ここで、元の質問に戻ります(現時点ではtype-oです)。

Class&
Class::operator=(Class&& rhs)
{
    if (this == &rhs)  // is this check needed?
    {
       // ...
    }
    return *this;
}

これは実際には物議を醸す問題です。はい、絶対に言う人もいれば、いいえと言う人もいます。

私の個人的な見解は「いいえ」です。このチェックは必要ありません。

根拠:

オブジェクトが右辺値参照にバインドするとき、それは次の2つのいずれかです。

  1. 一時的なもの。
  2. 発信者があなたに信じてほしいオブジェクトは一時的なものです。

実際の一時的なオブジェクトへの参照がある場合、定義により、そのオブジェクトへの一意の参照があります。プログラム全体のどこからでも参照することはできません。すなわちthis == &temporary 不可能です。

クライアントがあなたに嘘をつき、あなたがそうでないときに一時的なものになると約束した場合、あなたが気にする必要がないことを確認するのはクライアントの責任です。あなたが本当に注意したいのであれば、私はこれがより良い実装になると信じています:

Class&
Class::operator=(Class&& other)
{
    assert(this != &other);
    // ...
    return *this;
}

つまり、自己参照渡された場合、これは修正すべきクライアント側のバグです。

完全を期すために、次の移動代入演算子を示しdumb_arrayます。

dumb_array& operator=(dumb_array&& other)
{
    assert(this != &other);
    delete [] mArray;
    mSize = other.mSize;
    mArray = other.mArray;
    other.mSize = 0;
    other.mArray = nullptr;
    return *this;
}

移動割り当ての一般的な使用例では、移動元*thisオブジェクトになるためdelete [] mArray;、何もしないでください。実装がnullptrをできるだけ速く削除することが重要です。

警告:

それswap(x, x)は良い考えだとか、単に必要な悪だと主張する人もいます。そして、これがスワップがデフォルトのスワップになった場合、自己移動割り当てを引き起こす可能性があります。

私は反対しswap(x, x)ている、これまで良いアイデア。自分のコードで見つかった場合は、パフォーマンスのバグと見なして修正します。ただし、許可する場合swap(x, x)は、移動元の値に対してのみ自己移動割り当てを行うことを認識してください。また、このdumb_array例では、アサートを省略するか、移動元のケースに制限するだけで、これは完全に無害です。

dumb_array& operator=(dumb_array&& other)
{
    assert(this != &other || mSize == 0);
    delete [] mArray;
    mSize = other.mSize;
    mArray = other.mArray;
    other.mSize = 0;
    other.mArray = nullptr;
    return *this;
}

2つのmoved-from(empty)を自分で割り当てた場合dumb_array、プログラムに無用な命令を挿入する以外は、何も問題はありません。この同じ観察は、大多数のオブジェクトに対して行うことができます。

<更新>

この問題についてもう少し考え、私の立場を少し変えました。今では、割り当ては自己割り当てに寛容である必要があると考えていますが、コピー割り当てと移動割り当てのポスト条件は異なります。

コピー割り当ての場合:

x = y;

の値をy変更してはならないという事後条件が必要です。そのとき&x == &y、この事後条件は次のように変換されます。自己コピーの割り当てはの値に影響を与えませんx

移動割り当ての場合:

x = std::move(y);

y有効であるが指定されていない状態を持つ事後条件が必要です。そのとき&x == &y、この事後条件は次のように変換されますx。有効ですが指定されていない状態です。つまり、自己移動割り当ては何もする必要はありません。しかし、クラッシュしないはずです。この事後条件はswap(x, x)、単に動作できるようにすることと一致しています。

template <class T>
void
swap(T& x, T& y)
{
    // assume &x == &y
    T tmp(std::move(x));
    // x and y now have a valid but unspecified state
    x = std::move(y);
    // x and y still have a valid but unspecified state
    y = std::move(tmp);
    // x and y have the value of tmp, which is the value they had on entry
}

上記が機能する限り、 x = std::move(x)クラッシュしないします。x有効だが不特定の状態のままになる可能性があります。

dumb_arrayこれを達成するために、代入代入演算子をプログラムする3つの方法があります。

dumb_array& operator=(dumb_array&& other)
{
    delete [] mArray;
    // set *this to a valid state before continuing
    mSize = 0;
    mArray = nullptr;
    // *this is now in a valid state, continue with move assignment
    mSize = other.mSize;
    mArray = other.mArray;
    other.mSize = 0;
    other.mArray = nullptr;
    return *this;
}

上記の実装では、自己の割り当てを許容するが、*thisおよびother自己移動割り当て後のゼロサイズのアレイの元の値がどのように関係なくなってしまう*thisです。これで結構です。

dumb_array& operator=(dumb_array&& other)
{
    if (this != &other)
    {
        delete [] mArray;
        mSize = other.mSize;
        mArray = other.mArray;
        other.mSize = 0;
        other.mArray = nullptr;
    }
    return *this;
}

上記の実装では、コピー代入演算子を操作なしにすることで、コピー代入演算子と同じように自己代入を許容します。これも結構です。

dumb_array& operator=(dumb_array&& other)
{
    swap(other);
    return *this;
}

上記は大丈夫です dumb_array「即時」に破棄する必要があるリソースをが保持していないません。たとえば、唯一のリソースがメモリの場合、上記で問題ありません。もしdumb_arrayミューテックスロックまたはファイルのオープン状態を保持できる可能性がある、クライアントは移動割り当てのlh上のこれらのリソースがすぐに解放されることを合理的に期待できるため、この実装には問題がある可能性があります。

最初のコストは2つの追加のストアです。2番目のコストは、テストと分岐です。どちらも機能します。どちらも、C ++ 11標準の表22 MoveAssignable要件のすべての要件を満たしています。3番目は、メモリリソース以外の問題を法として機能します。

3つの実装はすべて、ハードウェアに応じて異なるコストがかかる可能性があります。レジスターはたくさんありますか、それともごくわずかですか?

持ち帰りは、自己コピー割り当てとは異なり、自己移動割り当ては現在の値を保持する必要がないということです。

</更新>

Luc Dantonのコメントに触発された最後の(うまくいけば)編集:

メモリを直接管理しない(ただし、ベースまたはメンバーが管理する可能性がある)高レベルのクラスを作成している場合、移動割り当ての最適な実装は次のとおりです。

Class& operator=(Class&&) = default;

これにより、各ベースと各メンバーが順番に割り当てられ、this != &otherチェックは含まれません。これにより、ベースとメンバー間で不変条件を維持する必要がないと仮定すると、最高のパフォーマンスと基本的な例外の安全性が得られます。強力な例外安全性を要求するクライアントの場合、それらをに向けstrong_assignます。


6
私はこの答えについてどう感じるかわかりません。そのようなクラス(メモリを非常に明示的に管理するクラス)の実装は一般的なことのように見えます。このようなクラスを作成する場合、例外の安全性の保証について非常に注意深く、インターフェースのスイートスポットを簡潔かつ便利に見つける必要があること事実ですが、問題は一般的なアドバイスを求めているようです。
Luc Danton、

ええ、リソースやものを管理するクラスにとっては時間の浪費であるため、コピーアンドスワップは絶対に使用しません(なぜ、すべてのデータのコピー全体を作成するのですか?)。おかげで、これは私の質問に答えます。
セスカーネギー

5
移動割り当て-から自己をすべきだという提案のためDownvoted これまでのassert-失敗するか、または「未指定」の結果を生み出します。Assignment-from-selfは、文字通り正しく理解するための最も簡単なケースです。クラスがでクラッシュするstd::swap(x,x)場合、なぜより複雑な操作を正しく処理するためにそれを信頼する必要があるのですか?
Quuxplusone 2014

1
@Quuxplusone:私の回答の更新で述べられているように、私はassert-failについてあなたに同意するようになりました。限りでstd::swap(x,x)は、x = std::move(x)は不特定の結果を生成する場合でも動作します。それを試してみてください!私を信じる必要はありません。
ハワードHinnant 14

@HowardHinnantの良い点は、移動可能な状態のままになっswapている限り機能します。そしてそのx = move(x)xstd::copy /のstd::moveアルゴリズムは、無操作のコピーすでに上未定義の動作を生成するように定義されています(痛い; 20歳のmemmove平凡なケース権を取得しますが、std::moveしません!)。だから、私はまだ自己割り当てのための「スラムダンク」を考えていないと思います。しかし、明らかに、標準がそれを祝福しているかどうかにかかわらず、自己割り当ては実際のコードで多く発生します。
Quuxplusone 2014

11

まず、移動割り当て演算子の署名が間違っていました。moveはソースオブジェクトからリソースを盗むため、ソースはconstr値以外の参照である必要があります。

Class &Class::operator=( Class &&rhs ) {
    //...
    return *this;
}

(非constl値の参照を介して戻ることに注意してください。

どちらのタイプの直接割り当てでも、標準は自己割り当てをチェックすることではなく、自己割り当てがクラッシュアンドバーンを引き起こさないことを確認することです。一般的に、誰も明示しませんx = xy = std::move(y)つながる可能性があり、通話が、特に複数の機能を通じて、エイリアシングa = bc = std::move(d)自己割り当てをされて入って。自己割り当ての明示的なチェック、つまりthis == &rhstrueの場合に関数の主要部分をスキップすることは、自己割り当ての安全性を保証する1つの方法です。しかし、これは(うまくいけば)まれなケースを最適化するので、最悪の方法の1つです。

ここで、(少なくとも)オペランドの1つが直接一時オブジェクトである場合、自己割り当てシナリオを使用することはできません。一部の人々は、そのケースを想定し、コードを最適化して、想定が間違っている場合にコードが自殺的に愚かになってしまうことを主張しています。ユーザーに対して同じオブジェクトのチェックをダンプすることは無責任だと私は言います。コピー割り当てについては、そのような主張はしません。なぜ移動割り当ての位置を逆にするのですか?

別の回答者から変更された例を作ってみましょう:

dumb_array& dumb_array::operator=(const dumb_array& other)
{
    if (mSize != other.mSize)
    {
        delete [] mArray;
        mArray = nullptr;  // clear this...
        mSize = 0u;        // ...and this in case the next line throws
        mArray = other.mSize ? new int[other.mSize] : nullptr;
        mSize = other.mSize;
    }
    std::copy(other.mArray, other.mArray + mSize, mArray);
    return *this;
}

このコピー割り当ては、明示的なチェックなしで自己割り当てを適切に処理します。コピー元とコピー先のサイズが異なる場合、コピーの前に割り当て解除と再割り当てが行われます。それ以外の場合は、コピーのみが行われます。自己割り当ては最適化されたパスを取得せず、ソースと宛先のサイズが等しく開始されたときと同じパスにダンプされます。2つのオブジェクトが等しい場合(それらが同じオブジェクトの場合を含む)、コピーは技術的に不要ですが、同等のチェック(値またはアドレス)を行わない場合のコストです。当時の。ここでのオブジェクトの自己割り当ては、一連の要素レベルの自己割り当てを引き起こすことに注意してください。要素タイプはこれを行うために安全でなければなりません。

ソースの例と同様に、このコピー割り当ては、基本的な例外の安全性を保証します。強力な保証が必要な場合は、元のコピーとスワップクエリの統合割り当て演算子を使用して、コピーと移動の両方の割り当てを処理します。ただし、この例のポイントは、速度を上げるために安全性を1ランク下げることです。(ところで、個々の要素の値は独立していると想定しています。他の要素と比較して、いくつかの値を制限する不変の制約はありません。)

この同じタイプの移動割り当てを見てみましょう。

class dumb_array
{
    //...
    void swap(dumb_array& other) noexcept
    {
        // Just in case we add UDT members later
        using std::swap;

        // both members are built-in types -> never throw
        swap( this->mArray, other.mArray );
        swap( this->mSize, other.mSize );
    }

    dumb_array& operator=(dumb_array&& other) noexcept
    {
        this->swap( other );
        return *this;
    }
    //...
};

void  swap( dumb_array &l, dumb_array &r ) noexcept  { l.swap( r ); }

カスタマイズが必要なスワップ可能な型には、その型とswap同じ名前空間で呼び出される2つの引数のない関数が必要です。(名前空間の制限により、非修飾のswap呼び出しが機能します。)コンテナータイプはswap、標準コンテナーと一致するパブリックメンバー関数も追加する必要があります。メンバーならswapが提供されていないswapおそらく自由関数をスワップ可能なタイプのフレンドとしてマークする必要があります。使用する動きをカスタマイズする場合swapは、独自のスワッピングコードを提供する必要があります。標準コードは、型の移動コードを呼び出します。これにより、移動カスタマイズされた型の相互再帰が無限に発生します。

デストラクタと同様に、スワップ関数と移動操作は、可能な場合は決してスローせず、おそらくC ++ 11でそのようにマークする必要があります。標準ライブラリのタイプとルーチンには、スローできない移動タイプの最適化があります。

移動割り当てのこの最初のバージョンは、基本契約を満たしています。ソースのリソースマーカーが宛先オブジェクトに転送されます。古いリソースは、ソースオブジェクトで管理されるため、リークされません。そして、ソースオブジェクトは、割り当てや破棄などの追加の操作を適用できる、使用可能な状態のままになります。

この移動割り当ては、自己割り当てに対して自動的に安全であることに注意してください。 swap呼び出しが。また、例外に対しても安全です。問題は、不要なリソースの保持です。送信先の古いリソースは概念的には不要になりましたが、ここではまだ残っているため、送信元オブジェクトは有効なままです。ソースオブジェクトの破棄が予定されている場合は、リソーススペースが無駄になります。リソーススペースの合計が制限されていて、(新しい)ソースオブジェクトが正式に死ぬ前に他のリソース請願が発生する場合はさらに悪くなります。

この問題が原因で、移動割り当て時のセルフターゲティングに関して、現在問題となっているグルのアドバイスが発生しています。リソースを残さずに移動割り当てを作成する方法は、次のようなものです。

class dumb_array
{
    //...
    dumb_array& operator=(dumb_array&& other) noexcept
    {
        delete [] this->mArray;  // kill old resources
        this->mArray = other.mArray;
        this->mSize = other.mSize;
        other.mArray = nullptr;  // reset source
        other.mSize = 0u;
        return *this;
    }
    //...
};

ソースはデフォルトの状態にリセットされますが、古い宛先リソースは破棄されます。自己割り当ての場合、現在のオブジェクトは最終的に自殺します。それを回避する主な方法は、アクションコードをif(this != &other)ブロックで囲むか、またはそれをねじ込んで、クライアントにassert(this != &other)最初の行を食べさせることです(もし気分が良ければ)。

別の方法は、統合割り当てなしでコピー割り当てを非常に安全に例外にする方法を検討し、それを移動割り当てに適用することです。

class dumb_array
{
    //...
    dumb_array& operator=(dumb_array&& other) noexcept
    {
        dumb_array  temp{ std::move(other) };

        this->swap( temp );
        return *this;
    }
    //...
};

otherthisが異なる場合other、への移動によって空になり、そのままtempです。次に、によって最初に保持されていたリソースを取得thistempながら、に古いリソースを失いotherます。それから、thisいつ殺されるかの古いリソースtemp

自己割り当てが発生すると、が空othertempなりますthis。次に、ターゲットオブジェクトはリソースを取り戻しtempthisスワップします。の死はtemp空のオブジェクトを主張し、それは実質的に何もしないはずです。this/ otherオブジェクトは、そのリソースを保持します。

移動割り当ては、移動構築とスワッピングもそうである限り、決してスローされるべきではありません。自己割り当て時にも安全であることのコストは、低レベルの型よりもさらにいくつかの命令です。これは、割り当て解除の呼び出しによって一掃される必要があります。


delete2番目のコードブロックを呼び出す前に、メモリが割り当てられているかどうかを確認する必要が ありますか?
user3728501 2015

3
2番目のコードサンプル、自己代入チェックのないコピー代入演算子は間違っています。std::copyソースとデスティネーションの範囲が重複している場合(それらが一致する場合を含む)、未定義の動作が発生します。C ++ 14 [alg.copy] / 3を参照してください。
MM

6

私は、自己割り当てセーフオペレーターを望んでいるが、の実装で自己割り当てチェックを記述したくないというキャンプにいますoperator=。実際、私はまったく実装operator=したくありません。デフォルトの動作が「そのまま」機能するようにしたいのです。最高の特別メンバーは無料で来るメンバーです。

とはいえ、標準に存在するMoveAssignable要件は次のように記述されています(17.6.3.1テンプレート引数要件[utility.arg.requirements]、n3290から):

式戻り値の型戻り値事後条件
t = rv T&ttは、割り当て前のrvの値と同等です。

プレースホルダーは次のように記述されます:「t[is a] T型の変更可能な左辺値;」そして「rvT型の右辺値です;」。これらは標準ライブラリのテンプレートへの引数として使用される型に課される要件であることに注意してください。ただし、標準の他の場所を見ると、移動割り当てに関するすべての要件がこれと似ていることがわかります。

これはa = std::move(a)「安全」でなければならないことを意味します。必要なものがアイデンティティテスト(例this != &other:)の場合、それを実行しないと、オブジェクトをに入れることさえできませんstd::vector。(MoveAssignableを必要とするメンバー/操作を使用しない場合を除き、決して気にしないでください。)前の例a = std::move(a)では、this == &otherが実際に保持されることに注意してください。


機能しa = std::move(a)ないとクラスが機能しなくなる原因を説明できますstd::vectorか?例?
Paul J. Lucas

MovePasignable std::vector<T>::eraseでない限り、@ PaulJ.Lucasの呼び出しは許可されませんT。(IIRCとは別に、C ++ 14では、一部のMoveAssignable要件がMoveInsertableに緩和されました。)
Luc Danton

OK、そうでTなければMoveAssignableでなければなりませんが、なぜerase()要素をそれ自体に移動することに依存するのですか?
Paul J. Lucas

@ PaulJ.Lucasその質問に対する満足のいく答えはありません。すべては、「契約を破らないこと」に要約されます。
Luc Danton、2015年

2

現在のoperator=関数が作成されると、右辺値参照引数を作成したのでconst、ポインターを「盗んで」、受信する右辺値参照の値を変更する方法はありません。単に変更することはできません。そこから読み取ることしかできません。通常のlvaue-reference メソッドの場合と同様deleteに、thisオブジェクトでポインターなどを呼び出すようにした場合にのみ問題が発生しますがoperator=、これは右辺値バージョンのポイントを無効にします...つまり、基本的にconst-lvalue operator=メソッドに残されているのと同じ操作を基本的に行うために、右辺値バージョンを使用するのは冗長です。

ここでoperator=、非const右辺値参照を取るように定義した場合、チェックが必要であると確認できる唯一の方法thisは、一時値ではなく意図的に右辺値参照を返す関数にオブジェクトを渡した場合です。

たとえば、誰かがoperator+関数を書き、rvalue参照とlvalue参照の混合を利用して、オブジェクトタイプの積み重ね加算操作中に余分な一時変数が作成されないようにしたとします。

struct A; //defines operator=(A&& rhs) where it will "steal" the pointers
          //of rhs and set the original pointers of rhs to NULL

A&& operator+(A& rhs, A&& lhs)
{
    //...code

    return std::move(rhs);
}

A&& operator+(A&& rhs, A&&lhs)
{
    //...code

    return std::move(rhs);
}

int main()
{
    A a;

    a = (a + A()) + A(); //calls operator=(A&&) with reference bound to a

    //...rest of code
}

さて、右辺値参照について私が理解していることから、上記を行うことはお勧めしません(つまり、右辺値参照ではなく一時的な値を返す必要があります)が、誰かがそれをまだ行う場合は、受信する右辺値参照がthisポインタと同じオブジェクトを参照していないことを確認してください。


「a = std :: move(a)」は、このような状況に陥る簡単な方法です。あなたの答えは有効です。
Vaughn Cato、2012

1
ほとんどの人は意図的にそれをしないと思いますが、それが最も簡単な方法であることに完全に同意します:-) ...ただし、右辺値参照がの場合はconst、そこから読み取ることしかできないため、必要なのはスワップスタイルの操作ではなく、一般的な方法でoperator=(const T&&)行うのと同じ再初期化を実行することを決定した場合(つまり、深いコピーを作成するのではなく、ポインタを盗むなど)、チェックを行います。thisoperator=(const T&)
ジェイソン

1

私の答えはまだ移動割り当ては自己割り当てに対して保存する必要はありませんが、それは別の説明があります。std :: unique_ptrを検討してください。私がそれを実装するとしたら、私はこのようなことをするでしょう:

unique_ptr& operator=(unique_ptr&& x) {
  delete ptr_;
  ptr_ = x.ptr_;
  x.ptr_ = nullptr;
  return *this;
}

これを説明しているスコット・マイヤーズを見ると、彼は似たようなことをしています。(あなたがスワップをしない理由をさまよう場合-それは1つの追加の書き込みがあります)。そして、これは自己割り当てには安全ではありません。

時々これは残念です。ベクトルからすべての偶数を移動することを検討してください。

src.erase(
  std::partition_copy(src.begin(), src.end(),
                      src.begin(),
                      std::back_inserter(even),
                      [](int num) { return num % 2; }
                      ).first,
  src.end());

これは整数の場合は問題ありませんが、移動のセマンティクスでこのようなものを作成できるとは思いません。

結論としては、オブジェクト自体への割り当ての移動は適切ではなく、注意する必要があります。

小さな更新。

  1. 私はハワードに同意しません。これは悪い考えですが、それでも、「移動した」オブジェクトの自己移動割り当ては機能するはずswap(x, x)です。アルゴリズムはこれらのものが大好きです!コーナーケースが機能するのはいつでも素晴らしいことです。(そして私はそれが無料ではない場合をまだ見ていません。それが存在しないという意味ではありません)。
  2. これは、unique_ptrsの割り当てがlibc ++でどのように実装されるか unique_ptr& operator=(unique_ptr&& u) noexcept { reset(u.release()); ...} です。自己移動割り当ての場合は安全です。
  3. コアガイドラインでは、割り当てを自分で移動しても問題ないと考えています。

0

(this == rhs)考えられる状況があります。このステートメントの場合:Myclass obj; std :: move(obj)= std :: move(obj)


Myclass obj; std :: move(obj)= std :: move(obj);
little_monster 2017
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.