この回答で説明されているように、コピー アンド スワップ イディオムは次のように実装されます。
class MyClass
{
private:
BigClass data;
UnmovableClass *dataPtr;
public:
MyClass()
: data(), dataPtr(new UnmovableClass) { }
MyClass(const MyClass& other)
: data(other.data), dataPtr(new UnmovableClass(*other.dataPtr)) { }
MyClass(MyClass&& other)
: data(std::move(other.data)), dataPtr(other.dataPtr)
{ other.dataPtr= nullptr; }
~MyClass() { delete dataPtr; }
friend void swap(MyClass& first, MyClass& second)
{
using std::swap;
swap(first.data, other.data);
swap(first.dataPtr, other.dataPtr);
}
MyClass& operator=(MyClass other)
{
swap(*this, other);
return *this;
}
};
operator= のパラメーターとして MyClass の値を持つことにより、コピー コンストラクターまたはムーブ コンストラクターのいずれかによってパラメーターを構築できます。その後、パラメーターからデータを安全に抽出できます。これにより、コードの重複が防止され、例外の安全性が向上します。
答えは、変数を一時的に交換または移動できることを示しています。主にスワッピングについて説明します。ただし、コンパイラによって最適化されていない場合、スワップには 3 つの移動操作が含まれ、より複雑なケースでは追加の作業が必要になります。必要な場合は、一時オブジェクトを割り当て先オブジェクトに移動するだけです。
オブザーバー パターンを含む、このより複雑な例を考えてみましょう。この例では、代入演算子のコードを手動で記述しました。move コンストラクター、代入演算子、swap メソッドに重点が置かれています。
class MyClass : Observable::IObserver
{
private:
std::shared_ptr<Observable> observable;
public:
MyClass(std::shared_ptr<Observable> observable) : observable(observable){ observable->registerObserver(*this); }
MyClass(const MyClass& other) : observable(other.observable) { observable.registerObserver(*this); }
~MyClass() { if(observable != nullptr) { observable->unregisterObserver(*this); }}
MyClass(MyClass&& other) : observable(std::move(other.observable))
{
observable->unregisterObserver(other);
other.observable.reset(nullptr);
observable->registerObserver(*this);
}
friend void swap(MyClass& first, MyClass& second)
{
//Checks for nullptr and same observable omitted
using std::swap;
swap(first.observable, second.observable);
second.observable->unregisterObserver(first);
first.observable->registerObserver(first);
first.observable->unregisterObserver(second);
second.observable->registerObserver(second);
}
MyClass& operator=(MyClass other)
{
observable->unregisterObserver(*this);
observable = std::move(other.observable);
observable->unregisterObserver(other);
other.observable.reset(nullptr);
observable->registerObserver(*this);
}
}
明らかに、この手動で記述された代入演算子のコードの重複部分は、ムーブ コンストラクターの部分と同じです。代入演算子でスワップを実行すると動作は正しくなりますが、より多くの移動が実行され、余分な登録 (スワップ内) と登録解除 (デストラクタ内) が実行される可能性があります。
代わりに、ムーブ コンストラクターのコードを再利用する方がはるかに理にかなっているのではないでしょうか?
private:
void performMoveActions(MyClass&& other)
{
observable->unregisterObserver(other);
other.observable.reset(nullptr);
observable->registerObserver(*this);
}
public:
MyClass(MyClass&& other) : observable(std::move(other.observable))
{
performMoveActions(other);
}
MyClass& operator=(MyClass other)
{
observable->unregisterObserver(*this);
observable = std::move(other.observable);
performMoveActions(other);
}
このアプローチは、スワップ アプローチよりも決して劣っていないように見えます。C++ 11 では、コピー アンド スワップ イディオムの方がコピー アンド ムーブ イディオムとして適していると考えるのは正しいですか、それとも何か重要なことを見逃していましたか?