18

私が理解しているように、operator =をオーバーロードする場合、戻り値は非定数参照である必要があります。


A& A::operator=( const A& )
{
    // check for self-assignment, do assignment

    return *this;
}

次のような場合にnon-constメンバー関数を呼び出せるようにすることはnon-constです。


( a = b ).f();

しかし、なぜそれは参照を返す必要がありますか?戻り値が参照として宣言されていない場合、どのような場合に問題が発生しますか?たとえば、値による戻りとしましょう。

コピーコンストラクタが正しく実装されていることを前提としています。

4

10 に答える 10

18

参照を返さないことはリソースの浪費であり、奇妙なデザインになります。ほとんどすべてのユーザーがその値を破棄する場合でも、オペレーターのすべてのユーザーに対してコピーを実行するのはなぜですか?

a = b; // huh, why does this create an unnecessary copy?

さらに、組み込みの代入演算子は同様にコピーしないため、クラスのユーザーにとっては驚くべきことです。

int &a = (some_int = 0); // works
于 2010-03-15T14:11:23.500 に答える
16

演算子をオーバーロードするときの一般的なアドバイスは、「プリミティブ型と同じように行う」であり、プリミティブ型への代入のデフォルトの動作はそれです。

必要に応じて他の式内での割り当てを無効にするために、何も返さないこともできますが、コピーを返すことはまったく意味がありません。呼び出し元がコピーを作成したい場合は、参照からコピーを作成できます。コピーは必要ありません。不要な一時的なものを生成する必要はありません。

于 2010-03-15T14:14:03.727 に答える
4

原因f()はaを変更できます。(非 const 参照を返します)

a の値 (コピー) を返す場合、f()はaではなくコピーを変更します

于 2010-03-15T14:15:57.360 に答える
3

どのくらいの頻度でそれを実行したいかはわかりませんが、次のようなものが機能(a=b)=c;するための参照が必要です。

編集:さて、それよりも少し多くのことがあります。推論の多くは半歴史的です。一時オブジェクトへの不要なコピーを回避する以外にも、右辺値を返したくない理由はたくさんあります。Andrew Koenigによって最初に投稿された例の(マイナーな)バリエーションを使用して、次のようなものを検討してください。

struct Foo { 
    Foo const &assign(Foo const &other) { 
        return (*this = other);
    }
};

ここで、割り当てが右辺値を返した古いバージョンのC++を使用していると仮定します。その場合、(*this=other);はその一時的なものになります。次に、一時への参照をバインドし、一時を破棄し、最後に破棄された一時へのぶら下がっている参照を返します。

(参照を初期化するために使用される一時的なものの寿命を延ばす)以降に制定されたルールは、少なくともこの問題を軽減します(そして完全に解決する可能性があります)が、それらのルールが作成された後、誰もがこの特定の状況を再訪したとは思えません。それは、ハードウェアのさまざまなバージョンやバリエーションの数十のバグを回避するためのクラッジを含む醜いデバイスドライバーに少し似ていました-おそらくリファクタリングして単純化することができますが、一見無害に見える変更が現在何かを壊すかどうかは誰にもわかりません動作し、最終的には、彼らがそれを助けることができれば、誰もそれを見たくありません。

于 2010-03-15T14:11:12.243 に答える
2

代入演算子が const 参照パラメーターを取らない場合:

A& A::operator=(A&); // unusual, but std::auto_ptr does this for example.

または、クラスAに変更可能なメンバー (参照カウント?) がある場合、代入演算子が代入先だけでなく、代入先のオブジェクトも変更する可能性があります。次に、次のようなコードがあるとします。

a = b = c;

b = c割り当てが最初に行われ、 への参照を返す代わりに、値によってコピー (それを と呼びます)b'を返しますb。代入が完了するa = b'と、変更代入演算子はb'実数ではなくコピーを変更しますb

別の潜在的な問題 - 仮想代入演算子がある場合、参照ではなく値で返すとスライスが発生する可能性があります。それが良い考えだとは言いませんが、問題になる可能性があります。

そのようなことをするつもりなら、オブジェクトを変更した場合に一時的に変更されない(a = b).f()ように、参照によって返されるようにする必要があります。f()

于 2010-03-15T14:48:58.407 に答える
1

実際のコード(つまり、のようなもの(a=b)=cではない)では、値を返すことでコンパイルエラーが発生する可能性は低くなりますが、コピーの作成にはコストがかかることが多いため、コピーを返すことは非効率的です。

明らかに参照が必要な状況を思い付くことができますが、実際にはめったに(たとえあったとしても)それらが出てくることはありません。

于 2010-03-15T14:23:24.163 に答える
1

間違ったものを返すと意図しない副作用が静かに発生するのではないかと心配している場合は、operator=() returnを書くことができますvoid。私はこれを行うかなりのコードを見てきました (私は怠惰から、または「安全」ではなく戻り値の型がどうあるべきかわからないだけだと思います)、ほとんど問題を引き起こしません。によって通常返される参照を使用する必要がある種類の式は、operator=()ほとんど使用されず、ほとんどの場合、代替となる簡単なコードです。

戻ることを推奨するかどうかはわかりませんがvoid(コード レビューでは、おそらくすべきでないこととしてそれを呼び出すでしょう)、戻りたくない場合に検討するオプションとして、そこに投げ出しています。代入演算子の変わった使用法がどのように処理されるかについて心配する必要があります。


後期編集:

operator=()また、最初に、戻り値 a - を使用することで違いを分割できると述べたはずですが、const&それでも割り当てチェーンが許可されます。

a = b = c;

ただし、より珍しい用途のいくつかは許可されません。

(a = b) = c;

これにより、代入演算子は C と同様のセマンティクスを持つことに注意してください=。演算子によって返される値は左辺値ではありません。C++ では、=演算子が左オペランドの型を返すように標準が変更されたため、左辺値ですが、Steve Jessop が別の回答へのコメントで指摘したように、コンパイラが受け入れるようにします。

(a = b) = c;

aビルトインの場合でも、間にシーケンス ポイントがなく 2 回変更されるため、結果はビルトインの未定義の動作になります。関数呼び出しがシーケンス ポイントであるoperator=()ため、この問題は an を使用する非ビルトインでは回避されます。operator=()

于 2010-03-15T15:12:00.357 に答える
1

参照によって戻ると、連鎖操作を実行する時間が短縮されます。例):

a = b = c = d;

operator=値によって返される場合に呼び出されるアクションを見てみましょう。

  1. コピー代入 operator= forcは、一時的な無名オブジェクトを作成し、コピー ctor を呼び出しますc。と呼びましょう。dtc
  2. 次に、operator= forbが呼び出されます。右側のオブジェクトは tc です。移動代入演算子が呼び出されます。bと等しくなりtcます。そして、関数bは一時的な匿名にコピーします。それを と呼びましょうtb
  3. 同じことが再び起こり、a.operator=の一時コピーを返しますa。オペレーターの後;、3 つの一時オブジェクトはすべて破棄されます

全部で: コピー オペレーター 3 人、ムーブ オペレーター 2 人、コピー オペレーター 1 人

operator= が参照によって値を返す場合、何が変わるか見てみましょう。

  1. コピー代入演算子が呼び出されます。cと等しくなりd、左辺値オブジェクトへの参照が返されます
  2. 同じ。bと等しくなりc、左辺値オブジェクトへの参照が返されます
  3. 同じ。aと等しくなりb、左辺値オブジェクトへの参照が返されます

全体として、3 つのコピー オペレータのみが呼び出され、ctor はまったく呼び出されません。

さらに、const 参照によって値を返すことをお勧めします。これでは、トリッキーで自明でないコードを記述できなくなります。よりクリーンなコードを使用すると、バグを見つけるのがはるかに簡単になります:) ( a = b ).f();2行に分割することをお勧めしますa=b; a.f();

PS : コピー代入演算子: operator=(const Class& rhs).

移動代入演算子: operator=(Class&& rhs).

于 2016-04-27T20:53:31.527 に答える
1

これは Scott Meyers の優れた本、Effective C++の Item 10 です。からの参照を返すことoperator=は慣例にすぎませんが、それは良いことです。

これは単なる規則です。それに従わないコードはコンパイルされます。ただし、標準ライブラリのすべての型だけでなく、すべての組み込み型も規則に従います。違うことをする正当な理由がない限り、そうしないでください。

于 2010-03-15T15:18:12.190 に答える
0

コピーが返された場合は、ほとんどすべての重要なオブジェクトにコピーコンストラクターを実装する必要があります。

また、コピーコンストラクタをプライベートとして宣言し、代入演算子をパブリックのままにすると問題が発生します...クラスまたはそのインスタンスの外部で代入演算子を使用しようとすると、コンパイルエラーが発生します。

すでに述べたより深刻な問題は言うまでもありません。オブジェクトのコピーにする必要はありません。実際には、同じオブジェクトを参照する必要があります。一方への変更は両方に表示されるはずですが、コピーを返すと機能しません。

于 2010-03-15T14:23:57.093 に答える