コピーとスワップの慣用句はC++11でもまだ役に立ちますか?質問する

コピーとスワップの慣用句はC++11でもまだ役に立ちますか?質問する

私はこの質問を参照します:コピーアンドスワップの慣用句とは何ですか?

実際には、上記の回答は次の実装につながります。

class MyClass
{
public:
    friend void swap(MyClass & lhs, MyClass & rhs) noexcept;

    MyClass() { /* to implement */ };
    virtual ~MyClass() { /* to implement */ };
    MyClass(const MyClass & rhs) { /* to implement */ }
    MyClass(MyClass && rhs) : MyClass() { swap(*this, rhs); }
    MyClass & operator=(MyClass rhs) { swap(*this, rhs); return *this; }
};

void swap( MyClass & lhs, MyClass & rhs )
{
    using std::swap;
    /* to implement */
    //swap(rhs.x, lhs.x);
}

ただし、次のようにすると swap() を完全に回避できることに注意してください。

class MyClass
{
public:
    MyClass() { /* to implement */ };
    virtual ~MyClass() { /* to implement */ };
    MyClass(const MyClass & rhs) { /* to implement */ }
    MyClass(MyClass && rhs) : MyClass() { *this = std::forward<MyClass>(rhs);   }
    MyClass & operator=(MyClass rhs)
    { 
        /* put swap code here */ 
        using std::swap;
        /* to implement */
        //swap(rhs.x, lhs.x);
        // :::
        return *this;
    }
};

これは、MyClass の std::swap で有効な引数依存の検索ができなくなることを意味することに注意してください。

つまり、 swap() メソッドを持つことには何か利点があるのでしょうか。


編集:

上記の 2 番目の実装には大きな間違いがあることに気付きました。これはかなり大きな問題なので、これに遭遇した人全員に説明するためにそのままにしておきます。

演算子 = が次のように定義されている場合

MyClass2 & operator=(MyClass2 rhs)

そして rhs が右辺値であるときはいつでも、移動コンストラクタが呼び出されます。ただし、これは以下を使用する場合に意味します。

MyClass2(MyClass2 && rhs)
{
    //*this = std::move(rhs);
}

演算子 = が移動コンストラクターを呼び出すため、移動コンストラクターへの再帰呼び出しが行われることに注意してください...

これは非常に微妙で、実行時のスタック オーバーフローが発生するまで発見するのは困難です。

これを修正するには、

MyClass2 & operator=(const MyClass2 &rhs)
MyClass2 & operator=(MyClass2 && rhs)

これにより、コピーコンストラクタを次のように定義できます。

MyClass2(const MyClass2 & rhs)
{
    operator=( rhs );
}

MyClass2(MyClass2 && rhs)
{
    operator=( std::move(rhs) );
}

記述するコード量は同じで、コピー コンストラクターは「無料」で提供され、コピー コンストラクターの代わりに operator=(&) を、swap() メソッドの代わりに operator=(&&) を記述するだけであることに注意してください。

ベストアンサー1

まず第一に、やり方が間違っています。コピー アンド スワップの慣用句は、コンストラクターを代入演算子に再利用するためのもので (その逆ではありません)、既に適切に構築されているコンストラクター コードの利点を生かし、代入演算子の強力な例外安全性を保証します。ただし、移動コンストラクターでは swap を呼び出しません。コピー コンストラクターがすべてのデータをコピーするのと同じように (個々のクラスの特定のコンテキストでそれが何を意味するかは関係ありません)、移動コンストラクターはこのデータを移動し、移動コンストラクターは構築して割り当て/スワップを行います。

MyClass(const MyClass & rhs) : x(rhs.x) {}
MyClass(MyClass && rhs) : x(std::move(rhs.x)) {}
MyClass & operator=(MyClass rhs) { swap(*this, rhs); return *this; }

そして、これはあなたの別のバージョンでは

MyClass(const MyClass & rhs) : x(rhs.x) {}
MyClass(MyClass && rhs) : x(std::move(rhs.x)) {}
MyClass & operator=(MyClass rhs) { using std::swap; swap(x, rhs.x); return *this; }

これは、コンストラクタ内で代入演算子を呼び出すことによって発生する重大なエラーを示しません。コンストラクタ内で代入演算子を呼び出したり、オブジェクト全体を交換したりすることは絶対にしないでください。コンストラクタは構築を管理するために存在し、以前のデータがまだ存在しないため、そのデータの破壊を管理する必要がないという利点があります。同様に、コンストラクタはデフォルトで構築可能で最後の型を処理できますが、ないほとんどの場合、直接構築は、割り当て/スワップが続くデフォルト構築よりもパフォーマンスが向上する可能性があります。

しかし、あなたの質問に答えると、これはまだコピーアンドスワップの慣用句であり、明示的なswap関数がないだけです。そしてC++11では、コピーとスワップの両方を実装したため、さらに便利になりました。そして単一の関数による移動割り当て。

スワップ関数が代入演算子の外側でもまだ価値があるかどうかはまったく別の問題であり、この型がいずれにせよスワップされる可能性があるかどうかによって異なります。実際、C++11 では、適切な移動セマンティクスを持つ型は、デフォルトのstd::swap実装を使用して十分に効率的にスワップできるため、追加のカスタム スワップが不要になることがよくあります。代入演算子内でこのデフォルトを呼び出さないようにしてください。std::swapこれは、それ自体が移動代入を行うためです (移動コンストラクターの誤った実装と同じ問題につながります)。

しかし、もう一度言いますが、カスタムswap関数であろうとなかろうと、これはコピー アンド スワップ イディオムの有用性に何ら変化をもたらしません。これは C++11 ではさらに有用であり、追加の関数を実装する必要がなくなります。

おすすめ記事