Move constructor/operator=で尋ねたように、しばらくして、その質問に対する正しい答えに同意し、受け入れました。ちょうど考えていたのですが、移動時に呼び出される「移動デストラクタ」のようなものがあると便利でしょうか? move ctorまたはoperator =を使用するたびにオブジェクト。
このようにして、move dtor でのみ指定する必要があり、move コンストラクターによって使用された後にオブジェクトを無効にする方法を指定する必要があります。このセマンティクスがないと、 move ctor または operator= を記述するたびに、移動したオブジェクトを無効にする方法を明示的に記述する必要があります (コードの繰り返し/エラーの紹介)。この件に関するご意見をお待ちしております。
3 に答える
役に立つ具体例を教えてください。たとえば、私が理解している限り、移動割り当ては一般的に次のように実装される可能性があります
this->swap(rhv);
クラスが移動セマンティクスの恩恵を受ける場合、swap メソッドはおそらく有益です。*this
これにより、古いリソースを解放する作業が通常のデストラクタにうまく委任されます。
新しい種類のデストラクタが正しいコードを実現するエレガントな方法であることを示す特定の例がなければ、あなたの提案はあまり魅力的に見えません。
また、最新のリビジョンによると、移動コンストラクター/代入演算子はデフォルトにすることができます。これは、私のクラスが次のようになる可能性が非常に高いことを意味します。
class X
{
well_behaved_raii_objects;
public:
X(X&& ) = default;
X& operator=(X&&) = default;
};
デストラクタはまったくありません!代わりに2 つのデストラクタを使用することの魅力は何でしょうか?
また、代入演算子が処理する古いリソースを持っていることも考慮してください。現在の標準に従って、通常のデストラクタ呼び出しは、構築と代入の後、および IMO の両方で問題ないことに注意する必要があります。同様に、提案された移動デストラクタでは、同じ移動デストラクタができるコンストラクタと代入演算子で注意する必要があります。安全に呼び出されます。それとも、それぞれに 1 つずつ、2 つの移動デストラクタが必要ですか? :)
ムーブ コンストラクター/代入を使用して、コメント内のmsdn の例を作り直しました。
#include <algorithm>
class MemoryBlock
{
public:
// Simple constructor that initializes the resource.
explicit MemoryBlock(size_t length)
: length(length)
, data(new int[length])
{
}
// Destructor.
~MemoryBlock()
{
delete[] data; //checking for NULL is NOT necessary
}
// Copy constructor.
MemoryBlock(const MemoryBlock& other)
: length(other.length)
, data(new int[other.length])
{
std::copy(other.data, other.data + length, data);
}
// Copy assignment operator (replaced with copy and swap idiom)
MemoryBlock& operator=(MemoryBlock other) //1. copy resource
{
swap(other); //2. swap internals with the copy
return *this; //3. the copy's destructor releases our old resources
}
//Move constructor
//NB! C++0x also allows delegating constructors
//alternative implementation:
//delegate initialization to default constructor (if we had one), then swap with argument
MemoryBlock(MemoryBlock&& other)
: length(other.length)
, data(other.data)
{
other.data = 0; //now other can be safely destroyed
other.length = 0; //not really necessary, but let's be nice
}
MemoryBlock& operator=(MemoryBlock&& rhv)
{
swap(rhv);
//rhv now contains previous contents of *this, but we don't care:
//move assignment is supposed to "ruin" the right hand value anyway
//it doesn't matter how it is "ruined", as long as it is in a valid state
//not sure if self-assignment can happen here: if it turns out to be possible
//a check might be necessary, or a different idiom (move-and-swap?!)
return *this;
}
// Retrieves the length of the data resource.
size_t Length() const
{
return length;
}
//added swap method (used for assignment, but recommended for such classes anyway)
void swap(MemoryBlock& other) throw () //swapping a pointer and an int doesn't fail
{
std::swap(data, other.data);
std::swap(length, other.length);
}
private:
size_t length; // The length of the resource.
int* data; // The resource.
};
元の MSDN サンプルに関するいくつかのコメント:
1) 事前に NULL をチェックするdelete
必要はありません (おそらく、私が削除した出力に対してここで行われます。おそらく誤解を示しています)
2) 代入演算子のリソースの削除: コードの重複。コピー アンド スワップ イディオムを使用すると、以前に保持されたリソースを削除することはデストラクタに委譲されます。
3) コピー アンド スワップ イディオムは、自己割り当てチェックも不要にします。リソースが削除される前にコピーされても問題ありません。- (一方、「リソースを関係なくコピーする」ことは、このクラスで多くの自己割り当てが行われると予想される場合にのみ問題になります。)
4) MSDN の例の代入演算子には、あらゆる種類の例外安全性が欠けています。新しいストレージの割り当てに失敗した場合、クラスは無効なポインターで無効な状態のままになります。破棄すると、未定義の動作が発生します。
これは、ステートメントを慎重に並べ替え、その間に削除されたポインターを NULL に設定することで改善できます (残念ながら、この特定のクラスの不変条件は、常にリソースを保持しているようです。例外も完璧ではありません)。対照的に、コピー アンド スワップでは、例外が発生した場合、左辺の値は元の状態のままになります (操作は完了できませんが、データの損失は回避できます)。
5) ムーブ代入演算子では、自己代入チェックが特に疑わしく見えます。そもそも、左側の値が右側の値とどのように同じになるのかわかりません。アイデンティティを達成するのに時間がかかりますa = std::move(a);
か (とにかく未定義の動作になるように見えますか?)?
6) 繰り返しますが、move 割り当ては不必要にリソースを管理しています。私のバージョンでは、通常のデストラクタに委任するだけです。
結論: あなたが目にしているコードの重複は回避可能であり、単純な実装によってのみ導入されます (何らかの理由でチュートリアルで見られる傾向があります。おそらく、重複のあるコードは学習者にとって理解しやすいためです)。
リソース リークを防ぐには、移動代入演算子で常にリソース (メモリ、ファイル ハンドル、ソケットなど) を解放してください。
...コードの再複製が問題ない場合は、そうでない場合はデストラクタを再利用してください。
リソースの回復不可能な破壊を防ぐために、移動代入演算子で自己代入を適切に処理します。
... または、交換できることを確認する前に、何も削除しないようにしてください。というか、SO の質問: 明確に定義されたプログラムでの移動割り当ての場合、自己割り当てが発生する可能性はありますか。
さらに、私のドラフト (3092) から、クラスにユーザー定義のコピー コンストラクター/代入演算子がなく、移動コンストラクター/代入の存在を妨げるものが何もない場合、いずれかが暗黙的に defaulted として宣言されることがわかりました。私が間違っていなければ、これは次のことを意味します: メンバーが文字列、ベクター、shared_ptrs などの場合、通常はコピー コンストラクター/割り当てを記述しない場合、移動コンストラクター/移動割り当てを無料で取得できます。 .
これの何が問題なのですか:
struct Object
{
Object(Object&&o) { ...move stuff...; nullify_object(o); }
Object & operator = (Object && o) { ...; nullify_object(o); }
void nullify_object(Object && o);
};
または、ターゲットで nullify_object を呼び出す代わりに:o.nullify();
YANLF を追加しても大きなメリットはないと思います。
ムーブ コンストラクター/割り当てでは、リソースを盗み、盗まれたオブジェクトのリソースの状態を、デストラクタが呼び出されたときにオブジェクトを安全に破棄できる状態のままにします。移動コンストラクター/割り当てを除いて、リソースを盗む一時的な「値」を表示したり、アクセスしたりすることはできません。
例として、文字列を見てみましょう。ここで、一時オブジェクトから割り当てられたリソースとサイズを盗み、その値を独自の値に設定します (オブジェクトがデフォルトで構築されている場合は null と 0 にする必要があります)。