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


87

コピーコンストラクター以来

MyClass(const MyClass&);

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

MyClass& operator = (const MyClass&);

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


6
「...ほぼ同じコードを持っています...」?うーん...あなたは何か間違ったことをしているに違いない。このためにユーザー定義関数を使用する必要性を最小限に抑え、コンパイラーにすべての汚い作業を行わせるようにしてください。これは多くの場合、リソースを独自のメンバーオブジェクトにカプセル化することを意味します。コードを見せてください。たぶん私たちはいくつかの良いデザインの提案があります。
Sellibitze 2009年

回答:


121

はい。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;
}

A swapそれだけで内部の所有権を交換し、既存の状態をクリーンアップしたり、新しいリソースを割り当てる必要がないよう関数は、書き込みに一般的に簡単です。

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

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

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

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


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

14
たぶん「私はお勧めしません」に、「そしてC ++の専門家もしません」を追加します。誰かがやって来て、あなたが個人的なマイノリティの好みを表現しているだけでなく、実際にそれについて考えた人々の合意された意見を表明していることに気付かないかもしれません。そして、OK、多分私は間違っていて、C ++の専門家の中にはそれを推奨している人もいますが、個人的には、誰かがその推奨のリファレンスを考え出すための挑戦をします。
スティーブジェソップ

4
十分に公平です、とにかく私はすでにあなたに賛成しました:-)。何かがベストプラクティスと広く見なされている場合は、そのように言うのが最善だと思います(そして、結局のところ、それが本当に最善ではないと誰かが言った場合は、もう一度見てください)。同様に、誰かが「C ++でミューテックスを使用することは可能ですか」と尋ねた場合、「かなり一般的なオプションの1つは、RAIIを完全に無視し、本番環境でデッドロックする例外安全でないコードを作成することですが、作成することがますます一般的になっています。まともな、動作するコード ";-)
Steve Jessop

4
+1。そして、私は常に必要な分析があると思います。assign場合によっては、コピーctorと代入演算子の両方でメンバー関数を使用するのが合理的だと思います(軽量クラスの場合)。その他の場合(リソースを大量に消費する/使用する場合、ハンドル/本体)、もちろんコピー/スワップが最適です。
Johannes Schaub-litb 2009年

2
@litb:これに驚いたので、例外C ++の項目41を調べました(これはこれに変わりました)。この特定の推奨事項はなくなり、代わりにコピーアンドスワップを推奨しています。むしろこっそりと「問題#4:割り当てには非効率的」を同時に落としました。
CBベイリー

13

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

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

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

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

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


swap宣言する必要がありvirtualますか?

1
@Johannes:仮想関数はポリモーフィッククラス階層で使用されます。値型には代入演算子が使用されます。2つはほとんど混ざりません。
sbi 2016

-3

何か気になること:

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
デストラクタは失敗してはならないため、破壊時の例外は予期されていません。そして、移動が最も危険な操作である場合、破壊の背後に移動を移動することの利点は何でしょうか?つまり、標準スキームでは、移動に失敗しても古い状態が破損することはありませんが、新しいスキームでは破損します。なぜ?また、First, reading the word "swap" when my mind is thinking "copy" irritates->ライブラリライターとして、あなたは通常、一般的な方法(コピー+スワップ)を知っており、核心はmy mindです。あなたの心は実際にはパブリックインターフェイスの後ろに隠されています。それが再利用可能なコードのすべてです。
セバスチャンマッハ
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.