web-dev-qa-db-ja.com

コピー割り当て演算子が参照/定数参照を返す必要があるのはなぜですか?

C++では、コピー代入演算子から参照を返す概念は私には不明確です。コピー割り当て演算子が新しいオブジェクトのコピーを返せないのはなぜですか?さらに、クラスAと次のものがある場合:

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;
}
61
bks

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

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

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

64
Michael Burr

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!

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

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

54
Alex Collins

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

特に、通常はCと同様にoperator=のチェーンをサポートする必要があります。例えば:

int x, y, z;

x = y = z = 0;

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

Xへのconst参照を返すことは、一般的に悪い考えです。特に、const参照は一時オブジェクトにバインドできます。テンポラリのライフタイムは、バインドされている参照のライフタイムまで延長されますが、割り当てられるもののライフタイムまで再帰的にはなりません。これにより、ぶら下がり参照が簡単に返されます。const参照は一時オブジェクトにバインドされます。そのオブジェクトの存続期間は、参照の存続期間(関数の終わりで終了する)まで延長されます。関数が戻るまでに、参照と一時の寿命は終了しているため、割り当てられているのはぶら下がり参照です。

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

参照の代わりに値を返すには、理論的および実用的な問題があります。理論的には、=が通常意味するものと、この場合の意味との間に基本的な切断があります。特に、割り当てが通常「この既存のソースを取得してその値をこの既存の宛先に割り当てる」ことを意味する場合、「この既存のソースを取得してそのコピーを作成し、その値をこの既存の宛先に割り当てる」などの意味を持ち始めます。 」

実用的な観点から、特に右辺値参照が発明される前は、パフォーマンスに重大な影響を与える可能性があります。AからBへのコピー中に新しいオブジェクト全体を作成することは予期せず、非常に低速でした。たとえば、小さなベクトルがあり、それを大きなベクトルに割り当てた場合、小さなベクトルの要素と(小さな)固定オーバーヘッドをコピーして、サイズを調整するのに最大で時間がかかると予想されます宛先ベクトル。その代わりにtwoコピー、1つはソースからtemp、もう1つはtempからデスティネーション、そして(さらに悪いことに)一時的なベクトルの動的な割り当てが含まれる場合、操作の複雑さについての私の期待は- 全体破壊されました。小さいベクトルの場合、動的割り当ての時間は、要素をコピーする時間の何倍も簡単に長くなる可能性があります。

他の唯一のオプション(C++ 11で追加)は、右辺値参照を返すことです。これにより、予期しない結果が簡単に発生する可能性があります。a=b=c;のような連鎖割り当ては、bおよび/またはcの内容を破壊する可能性があり、これは非常に予期しないことです。

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

8
Jerry Coffin

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

5
Puppy

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

4
MSN

ユーザー定義の_operator=_の結果タイプにはコア言語の要件はありませんが、標準ライブラリには次のような要件があります。

C++ 98§23.1/ 3:

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

C++ 98§23.1/ 4:

表64では、Tはコンテナのインスタンス化に使用されるタイプ、tTの値、uは(おそらくconstTの値です。

enter image description here


値によるコピーを返すことは、割り当て演算子が右結合であるため、つまり_a = b = c = 42;_のような割り当てチェーンをサポートします。つまり、これはa = (b = (c = 42));として解析されます。ただし、コピーを返すと、_(a = b) = 666;_のような無意味な構造が禁止されます。小さいクラスの場合、コピーを返すのが最も効率的である可能性がありますが、大規模なクラスの場合、参照によって返すのが一般的に最も効率的です(コピーは非常に非効率的です)。

標準ライブラリの要件について知るまでは、効率と副作用に基づく不正なコードのサポートの不合理さを避けるために、_operator=_がvoidを返すようにしました。


C++ 11では、割り当て演算子をdefault- ingするために_T&_結果型の要件が追加されます。

C++ 11§8.4.2/ 1:

明示的にデフォルト設定されている関数は、宣言された関数タイプが同じでなければなりません(ただし、異なるref-qualifiersを除きます)コピーコンストラクターまたはコピー割り当て演算子の場合、パラメータータイプは「非const Tへの参照」である場合があります。ここで、Tはメンバー関数のクラスの名前です)暗黙的に宣言されたかのように