78

C++ では、コピー代入演算子から参照を返すという概念がよくわかりません。コピー代入演算子が新しいオブジェクトのコピーを返せないのはなぜですか? さらに、 classAがあり、次の場合:

A a1(param);
A a2 = a1;
A a3;

a3 = a2; //<--- this is the problematic line

operator=次のように定義されます。

A A::operator=(const A& a)
{
    if (this == &a)
    {
        return *this;
    }
    param = a.param;
    return *this;
}
4

7 に答える 7

72

厳密に言えば、コピー代入演算子の結果は参照を返す必要はありませんが、C++ コンパイラが使用するデフォルトの動作を模倣するために、代入先のオブジェクトへの非 const 参照 (暗黙的に生成されたコピー) を返す必要があります。代入演算子は非 const 参照を返します - C++03: 12.8/10)。コピー割り当てのオーバーロードから返されるコードをかなり見てきましたがvoid、それがいつ重大な問題を引き起こしたのか思い出せません。戻るvoidと、ユーザーは「割り当てチェーン」(a = b = c;)、たとえば、テスト式で代入の結果を使用することを防ぎます。この種のコードは決して前例のないものではありませんが、特に非プリミティブ型の場合は特に一般的だとは思いません (クラスのインターフェイスが iostream などのこれらの種類のテストを意図していない限り)。

これを行うことをお勧めしているわけではありません。許可されていることと、多くの問題が発生していないように見えることを指摘するだけです。

これらの他のSOの質問は関連しています(おそらく完全にだまされているわけではありません)、あなたが興味を持つかもしれない情報/意見があります。

于 2010-06-23T23:31:58.493 に答える
70

値による戻りよりも参照による戻りの方が望ましい理由について少し説明しoperator=ます ---a = b = c値が返された場合、チェーンは正常に動作するためです。

参照を返す場合、最小限の作業が行われます。あるオブジェクトの値が別のオブジェクトにコピーされます。

ただし、 の値で返す場合はoperator=、代入演算子が呼び出されるたびにコンストラクタとデストラクタを呼び出します!!

したがって、与えられた:

A& operator=(const A& rhs) { /* ... */ };

それで、

a = b = c; // calls assignment operator above twice. Nice and simple.

しかし、

A operator=(const A& rhs) { /* ... */ };

a = b = c; // calls assignment operator twice, calls copy constructor twice, calls destructor type to delete the temporary values! Very wasteful and nothing gained!

要するに、値を返すことによって得られるものは何もありませんが、失うものはたくさんあります。

(: これは、代入演算子が左辺値を返すことの利点に対処することを意図したものではありません。それが望ましい理由については、他の投稿をお読みください)

于 2011-01-23T01:41:42.527 に答える
11

をオーバーロードするoperator=と、任意の型を返すように記述できます。どうしてもやりたい場合は、オーバーロードX::operator=して(たとえば)まったく異なるクラスのインスタンスを返すことができますYまたはZ. ただし、これは一般的に非常にお勧めできません。

operator=特に、通常はC と同じように の連鎖をサポートする必要があります。例えば:

int x, y, z;

x = y = z = 0;

その場合、通常、割り当てられている型の左辺値または右辺値を返したいと思うでしょう。X への参照、X への const 参照、または X (値による) のいずれを返すかという問題だけが残ります。

X への const 参照を返すことは、一般的にはお勧めできません。特に、const 参照は一時オブジェクトにバインドできます。一時オブジェクトの存続期間は、それがバインドされている参照の存続期間まで延長されますが、割り当てられているものの存続期間まで再帰的に延長されることはありません。これにより、ダングリング参照を簡単に返すことができます。const 参照は一時オブジェクトにバインドされます。そのオブジェクトの有効期間は、参照の有効期間 (関数の最後で終了) まで延長されます。関数が戻るまでに、参照と一時の有効期間が終了しているため、割り当てられているのはダングリング参照です。

もちろん、const 以外の参照を返すことで、これに対する完全な保護が提供されるわけではありませんが、少なくとも作業が少し難しくなります。(たとえば) いくつかのローカルを定義し、それへの参照を返すこともできます (ただし、ほとんどのコンパイラはこれについて警告することができます)。

参照の代わりに値を返すことには、理論的および実際的な問題があります。=理論的な側面では、通常の意味とこの場合の意味との間に基本的な断絶があります。特に、割り当ては通常、「この既存のソースを取得して、その値をこの既存の宛先に割り当てる」ことを意味しますが、「この既存のソースを取得し、そのコピーを作成し、その値をこの既存の宛先に割り当てる」という意味になります。 "

実用的な観点から、特に右辺値参照が発明される前は、パフォーマンスに重大な影響を与える可能性がありました. A から B にコピーする過程でまったく新しいオブジェクトを作成することは予想外であり、多くの場合非常に遅くなりました. たとえば、小さなベクトルがあり、それを大きなベクトルに割り当てた場合、小さなベクトルの要素をコピーするのにせいぜい時間がかかり、サイズを調整するための (少し) 固定オーバーヘッドがかかると予想されます。宛先ベクトル。代わりに、 2 つのコピー (ソースから一時へのコピー、一時から宛先への別のコピー)、および (さらに悪いことに) 一時ベクトルの動的割り当てが含まれる場合、操作の複雑さについての私の期待は完全に破壊されました。ベクトルが小さい場合、動的割り当てにかかる時間は、要素をコピーする時間よりも何倍も長くなる可能性があります。

他の唯一のオプション (C++11 で追加) は、右辺値参照を返すことです。これは簡単に予期しない結果につながる可能性があります. のような連鎖代入はand/ora=b=c;の内容を破壊する可能性があります.これはまったく予期しないことです.bc

そのため、通常の参照 (const への参照でも右辺値参照でもない) を返すことが、ほとんどの人が通常必要とするものを (合理的に) 確実に生成する唯一のオプションとして残されます。

于 2014-11-18T02:04:07.830 に答える
5

これは、自己への参照を返す方が値で返すよりも高速であるためでもありますが、プリミティブ型に存在する元のセマンティクスを許可するためでもあります。

于 2010-06-23T21:52:12.277 に答える
4

operator=必要なものを返すように定義できます。問題が実際に何であるかについて、より具体的にする必要があります。コピーコンストラクターがoperator=内部で使用していて、スタックオーバーフローが発生しているのではないかと思います。これは、コピーコンストラクターが呼び出して、コピーコンストラクターを使用して値を無限operator=に返す必要があるためです。A

于 2010-06-23T22:07:09.193 に答える
3

user-defined の結果の型に関するコア言語要件はありませんoperator=が、標準ライブラリにはそのような要件があります。

C++98 §23.1/3:

これらのコンポーネントに格納されるオブジェクトのタイプは、タイプの要件CopyConstructible (20.1.3) およびAssignableタイプの追加要件を満たす必要があります。

C++98 §23.1/4:

表 64 で、Tはコンテナーのインスタンス化に使用される型であり、tの値でありTuは (おそらくconst)の値ですT

ここに画像の説明を入力


a = b = c = 42;代入演算子は右結合であるため、値によるコピーを返すことは、 のような代入連鎖をサポートします。つまり、これは として解析されa = (b = (c = 42));ます。しかし、コピーを返すと、 のような無意味な構造が禁止され(a = b) = 666;ます。小さなクラスの場合、コピーを返すのが最も効率的であると考えられますが、より大きなクラスの場合、参照によって返すのが一般的に最も効率的です (コピーは非常に非効率的です)。

標準ライブラリの要件について知るまでは、効率のためにoperator=returnを許可voidし、副作用に基づく悪いコードをサポートするという不条理を避けるために使用していました。


C++11 では、代入演算子を -ing するT&ための結果型の要件がさらにあります。default

C++11 §8.4.2/1:

明示的にデフォルト設定された関数は、[…] 宣言された同じ関数型を持つものとします (参照修飾子が異なる可能性を除き、コピー コンストラクターまたはコピー代入演算子の場合、パラメーターの型は「非参照への参照」である場合があります)。 const T」、ここTで、 はメンバー関数のクラスの名前です) あたかも暗黙的に宣言されたかのように

于 2014-11-18T02:14:00.690 に答える