2009-11-14 9 views
70

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

コピーコンストラクター

MyClass(const MyClass&); 

と=演算子オーバーロード

MyClass& operator = (const MyClass&); 

は同じコードを持ち、同じパラメーターを持ち、戻り値が異なるだけで、共通の関数それらの両方のために使用する?

+3

"...ほとんど同じコードを持っています..."うーん...あなたは間違ったことをしているに違いない。このためにユーザー定義関数を使用する必要性を最小限に抑え、コンパイラにすべての汚い作業をさせてください。これはしばしば、自分のメンバーオブジェクトにリソースをカプセル化することを意味します。いくつかのコードを表示することができます。たぶん私たちは良い設計提案をしているかもしれません。 – sellibitze

+1

[operator =とコピーコンストラクタの間のコードの重複を減らす]の可能な複製(http://stackoverflow.com/questions/1477145/reducing-code-duplication-between-operator-and-the-copy-constructor) – mpromonet

答えて

96

はい。一般的な選択肢は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が動作し、すべての基本タイプとポインタタイプで「ノースロー」が保証されています。ほとんどのスマートポインタは、ノースロー保証でスワップすることもできます。

+3

実際には、彼らは一般的な操作ではありません。コピー元が最初にオブジェクトのメンバーを初期化する間、代入演算子は既存の値をオーバーライドします。これを考慮すると、copy ctorからの 'operator ='は、実際にはかなり悪いです。なぜなら、最初にすべての値をデフォルト値に初期化し、直後に他のオブジェクトの値で上書きするからです。 – sbi

+0

私は操作ではなく共通オプションを言った。コピーコンストラクタから 'operator = 'を呼び出すのは良いことではないことに完全に同意しますが、それがどれほど一般的であるかを見るには、現実世界のコードを合理的に調べるだけです。 –

+0

Downvoters、説明する気に? –

11

コピーコンストラクタは、以前は生のメモリであったオブジェクトの初期化を行います。代入演算子OTOHは、既存の値を新しいもので上書きします。これは、しばしば、古いリソース(たとえばメモリ)を破棄し、新しいリソースを割り当てることです。

2つの間に類似点がある場合は、代入演算子が破壊とコピー構築を実行することです。いくつかの開発者は、インプレース破棄とそれに続く配置コピー構築による割り当てを実際に実装していました。しかし、これは非常にの悪い考えです。(何この場合は、派生クラスの割り当て時に呼ばれる基本クラスの代入演算子のですか?)通常、今日チャールズとしてswapを使用している標準的なイディオムと考えられています何

が提案:

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

これはコピーを使用しています破壊の前に構築(失敗するかもしれない)する必要があります(失敗しないでください)。

+0

'swap'を' virtual'と宣言するべきですか? – nonplus

+1

@Johannes:仮想関数は多態的なクラス階層で使用されます。代入演算子は値型に使用されます。 2つはほとんど混合しない。 – sbi

-2

何かがについての私を気に:私の心は、「コピー」を考えているとき、単語「スワップ」を読んで、

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}を提案します。最も危険な行動であるこの動きは、他のすべてが解決された後に最後に取られたものである。

はい、どちらの方式でも破壊には問題があります。データが壊れている(あなたがそれとは思わなかったときにコピーされた)か、紛失していた(あなたがそうは思わなかったときに解放された)。失われたものは壊れたものよりも優れています。悪いデータよりも優れたデータはありません。

スワップの代わりに転送。それはとにかく私の提案です。

+2

デストラクタは失敗してはならないので、破壊時の例外は期待できません。 移動が最も危険な操作である場合、破壊の背後にある移動を移動する利点はありませんか?すなわち、標準的なスキームでは、移動失敗は古い状態を損なうことはありませんが、新しいスキームは失敗します。なぜ? また、最初に、私の心が「コピーする」と思っているときに「スワップ」という言葉を読んでください.->図書館の作家として、あなたは通常、共通の習慣(コピー+スワップ)を知っています。あなたの心は、実際には公開インターフェースの背後に隠されています。これが、再利用可能なコードのすべてです。 –

関連する問題