17

私はこの質問を参照します: コピーアンドスワップイディオムとは何ですか?

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

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 が右辺値である場合はいつでも、move コンストラクターが呼び出されます。ただし、これは次のことを意味します。

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

operator= が move コンストラクターを呼び出すため、move コンストラクターへの再帰呼び出しになることに注意してください...

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

これに対する修正は、両方を持つことです

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=(&&) を記述するだけであることに注意してください。

4

4 に答える 4

22

まず第一に、とにかくあなたはそれを間違っています。コピー アンド スワップ イディオムは、代入演算子のコンストラクターを再利用するためにあり (その逆ではありません)、コンストラクター コードを既に適切に構築し、代入演算子の強力な例外安全性を保証することから利益を得ています。ただし、移動コンストラクターで 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 では、コピー代入と移動代入の両方を 1 つの関数で実装したため、さらに便利です。

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

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

于 2012-10-17T10:48:56.730 に答える
2

あなたは確かに全体像を考慮していません。コードはさまざまな代入演算子にコンストラクターを再利用します。元のコードはさまざまなコンストラクターに代入演算子を再利用します。これは基本的に同じことです。変更しただけです。

ただし、コンストラクターを作成するため、デフォルトで構築できない型、または明示的に初期化されていない場合は値が悪い型、またはintデフォルトで構築するのに単純にコストがかかる型、またはデフォルトで構築されたメンバーが破棄するのに有効でない型を処理できます (たとえば、スマート ポインターを考えてみましょう。初期化されていない T* は不適切な削除につながります)。

基本的に、あなたが達成したのは同じ原則ですが、明らかに悪い場所にあります。ああ、4 つの関数すべてを定義する必要がありました。それ以外の場合は相互再帰ですが、元のコピー アンド スワップでは 3 つの関数しか定義されていませんでした。

于 2012-10-17T10:08:09.850 に答える
1

コピー アンド スワップ イディオムを使用してコピー代入を実装する理由 (ある場合) の有効性は、C++11 でも以前のバージョンと同じです。

またstd::move、移動コンストラクターのメンバー変数で使用する必要std::moveがあり、関数パラメーターである右辺値参照で使用する必要があることに注意してください。

std::forward必要に応じて右辺値または左辺値を保持するために、フォームのテンプレート パラメーター参照T&&と変数 (どちらも型推定中に左辺値参照への参照折りたたみの対象となる場合があります) にのみ使用する必要があります。auto&&

于 2012-10-17T10:11:50.820 に答える
-1

mebest では、C++11 で次のようにします。

class MyClass {
public:
    MyClass(MyString&& pStr) : str(pStr)) { 
        // call MyString(MyString&& pStr)
    }
    MyClass(const MyString& pStr) : str(pStr)) { 
        // call MyString(const MyString& pStr)
    }
private:
    MyString const str; // Still const!
};
于 2016-11-25T00:20:46.707 に答える