コピーコンストラクター以来
MyClass(const MyClass&);
および = 演算子のオーバーロード
MyClass& operator = (const MyClass&);
ほとんど同じコード、同じパラメーターを持ち、戻り値のみが異なります。両方が使用する共通の関数を持つことは可能ですか?
コピーコンストラクター以来
MyClass(const MyClass&);
および = 演算子のオーバーロード
MyClass& operator = (const MyClass&);
ほとんど同じコード、同じパラメーターを持ち、戻り値のみが異なります。両方が使用する共通の関数を持つことは可能ですか?
はい。2 つの一般的なオプションがあります。1 つ (一般的に推奨されない) はoperator=
、コピー コンストラクターから明示的に呼び出すことです。
MyClass(const MyClass& other)
{
operator=(other);
}
ただし、operator=
古い状態や自己割り当てから生じる問題に対処する場合、良いものを提供することは課題です。また、 from に割り当てられる場合でも、すべてのメンバーとベースが最初にデフォルトで初期化されother
ます。これは、すべてのメンバーとベースに対して有効であるとは限りません。有効な場合でも、意味的に冗長であり、実質的にコストがかかる場合があります。
ますます一般的なソリューションはoperator=
、コピー コンストラクターと swap メソッドを使用して実装することです。
MyClass& operator=(const MyClass& other)
{
MyClass tmp(other);
swap(tmp);
return *this;
}
あるいは:
MyClass& operator=(MyClass other)
{
swap(other);
return *this;
}
関数は通常、内部のswap
所有権を交換するだけで、既存の状態をクリーンアップしたり、新しいリソースを割り当てたりする必要がないため、簡単に記述できます。
コピーとスワップのイディオムの利点は、自動的に自己代入に対して安全であり、スワップ操作がスローされない場合、強力な例外セーフでもあることです。
強力な例外安全性を確保するために、「手書き」の代入演算子は、通常、担当者の古いリソースの割り当てを解除する前に、新しいリソースのコピーを割り当てる必要があります。これにより、新しいリソースの割り当てで例外が発生した場合でも、古い状態を元に戻すことができます。 . これらはすべてコピーアンドスワップで無料で提供されますが、通常はより複雑で、最初から行うとエラーが発生しやすくなります。
注意すべきことの 1 つは、swap メソッドが真のスワップでありstd::swap
、コピー コンストラクターと代入演算子自体を使用するデフォルトではないことを確認することです。
通常、メンバーごとswap
に使用されます。std::swap
動作し、すべての基本型とポインター型で「非スロー」が保証されます。ほとんどのスマート ポインターは、非スロー保証でスワップすることもできます。
コピー コンストラクターは、以前は raw メモリであったオブジェクトの初回の初期化を実行します。代入演算子 OTOH は、既存の値を新しい値で上書きします。多くの場合、これには古いリソース (メモリなど) の破棄と新しいリソースの割り当てが含まれます。
2 つに類似点があるとすれば、それは代入演算子が破棄とコピー構築を実行することです。一部の開発者は、インプレース破棄とそれに続く配置コピー構築によって実際に割り当てを実装していました。ただし、これは非常に悪い考えです。(これが、派生クラスの割り当て中に呼び出された基本クラスの割り当て演算子である場合はどうなりますか?)
swap
チャールズが提案したように、現在標準的なイディオムと通常考えられているものは次のとおりです。
MyClass& operator=(MyClass other)
{
swap(other);
return *this;
}
これは、コピー構築 (コピーされることに注意してくださいother
) と破棄 (関数の最後で破棄されることに注意してください) を使用します。また、それらを正しい順序で使用します: 構築 (失敗する可能性があります) の前に破棄 (失敗してはなりません)。
何か気になります:
MyClass& operator=(const MyClass& other)
{
MyClass tmp(other);
swap(tmp);
return *this;
}
まず、「コピー」と頭で考えているときに「スワップ」という言葉を読むと、私の常識が刺激されます。また、この巧妙なトリックの目的にも疑問があります。はい、新しい (コピーされた) リソースを構築する際の例外は、スワップの前に発生する必要があります。これは、ライブにする前に新しいデータがすべて満たされていることを確認する安全な方法のようです。
それはいいです。では、スワップ後に発生する例外についてはどうでしょうか? (一時オブジェクトがスコープ外になったときに古いリソースが破棄されたとき) 割り当てのユーザーの観点からは、操作は失敗しましたが、そうではありませんでした。これには大きな副作用があります。コピーが実際に行われたのです。失敗したのは、一部のリソースのクリーンアップだけでした。外部からは操作が失敗したように見えますが、宛先オブジェクトの状態が変更されています。
したがって、「スワップ」の代わりに、より自然な「転送」を行うことを提案します。
MyClass& operator=(const MyClass& other)
{
MyClass tmp(other);
transfer(tmp);
return *this;
}
一時オブジェクトの構築はまだありますが、次の即時アクションは、ソースのリソースをそこに移動する前に (およびそれらが二重に解放されないように NULL を設定する)、宛先の現在のすべてのリソースを解放することです。
{construct, move, destruct} の代わりに、{construct, destruct, move} を提案します。最も危険なアクションである移動は、他のすべてが解決された後に最後に実行されるものです。
はい、破壊の失敗はどちらのスキームでも問題です。データは破損している (思っていなかったときにコピーされた) か、失われた (思っていなかったときに解放された) かのいずれかです。失われることは、破損することよりも優れています。悪いデータほど優れたデータはありません。
スワップではなく転送します。とにかくそれが私の提案です。