web-dev-qa-db-ja.com

C ++でコンストラクタと=演算子のオーバーロードをコピー:一般的な機能は可能ですか?

コピーコンストラクター

MyClass(const MyClass&);

および=演算子のオーバーロード

MyClass& operator = (const MyClass&);

ほぼ同じコード、同じパラメーターを持ち、戻り値のみが異なる場合、両方が使用する共通の関数を使用することは可能ですか?

80
MPelletier

はい。 2つの一般的なオプションがあります。 1つ-一般に推奨されていません-コピーコンストラクターからoperator=を明示的に呼び出すことです:

MyClass(const MyClass& other)
{
    operator=(other);
}

ただし、古い状態と自己割り当てから生じる問題に対処する場合、operator=を適切に提供することは困難です。また、otherから割り当てられる場合でも、すべてのメンバーとベースはデフォルトで最初に初期化されます。これは、すべてのメンバーおよびベースに対しても有効ではない場合があり、有効である場合でも、意味的に冗長であり、実質的に高価になる場合があります。

ますます普及しているソリューションは、コピーコンストラクタとスワップメソッドを使用してoperator=を実装することです。

MyClass& operator=(const MyClass& other)
{
    MyClass tmp(other);
    swap(tmp);
    return *this;
}

あるいは:

MyClass& operator=(MyClass other)
{
    swap(other);
    return *this;
}

swap関数は、内部の所有権を交換するだけで、既存の状態をクリーンアップしたり、新しいリソースを割り当てたりする必要がないため、通常は記述が簡単です。

コピーとスワップのイディオムの利点は、自動的に自己割り当て安全であり、スワップ操作がスローされないことを条件として、非常に例外安全でもあることです。

非常に安全な例外であるため、「手書き」の割り当て演算子は、通常、割り当て先の古いリソースの割り当てを解除する前に新しいリソースのコピーを割り当てて、新しいリソースの割り当てで例外が発生した場合でも、古い状態を戻すことができるようにする必要があります。これはすべて、コピーアンドスワップで無料で提供されますが、通常はより複雑であり、エラーが発生しやすいため、最初から行う必要があります。

注意すべきことは、スワップコンストラクターが真のスワップであり、コピーコンストラクターと代入演算子自体を使用するデフォルトのstd::swapではないことを確認することです。

通常、メンバーごとのswapが使用されます。 std::swapは機能し、すべての基本型およびポインター型で「ノースロー」が保証されています。ほとんどのスマートポインターは、スローなしの保証で交換することもできます。

111
CB Bailey

コピーコンストラクターは、以前はrawメモリであったオブジェクトの初期化を初めて実行します。割り当て演算子OTOHは、既存の値を新しい値で上書きします。多くの場合、これには古いリソース(メモリなど)の破棄と新しいリソースの割り当てが含まれます。

2つの間に類似性がある場合、割り当て演算子は破棄とコピー構築を実行します。一部の開発者は、インプレース破棄とそれに続く配置コピー構築によって実際に割り当てを実装していました。ただし、これはveryの悪い考えです。 (これが派生クラスの割り当て中に呼び出された基本クラスの割り当て演算子である場合はどうなりますか?)

チャールズが示唆したように、今日の標準的なイディオムは通常swapを使用しています

MyClass& operator=(MyClass other)
{
    swap(other);
    return *this;
}

これはcopy-construction(otherがコピーされることに注意してください)と破棄(関数の最後で破棄されます)を使用し、正しい順序で使用します:破棄(構築の失敗)失敗してはいけません)。

12
sbi