1

C++ でいくつかの演算子をオーバーロードする目的を理解しようとしています。
概念的には、割り当てステートメントは次の方法で簡単に実装できます。

  • 古いオブジェクトの破棄とそれに続く新しいオブジェクトのコピー構築
  • 新しいオブジェクトのコピー構築、古いオブジェクトとの交換、古いオブジェクトの破棄

実際、多くの場合、コピー アンド スワップの実装、実際のコードでの代入の実装です。

では、なぜ C++ ではプログラマーが上記を実行する代わりに代入演算子をオーバーロードできるのでしょうか?

割り当てが破壊+建設よりも速いシナリオを許可することを意図していましたか?
もしそうなら、それはいつ起こりますか?そうでない場合、どのユースケースをサポートすることを意図していましたか?

4

6 に答える 6

2

1) 参照カウント

参照カウントされ、オブジェクトにラップされているリソースがあるとします。

void operator=(const MyObject& v) {
  this->resource = refCount(v->resource);
}

// Here the internal resource will be copied and not the objects.
MyObject A = B;

2) または、派手なセマンティクスなしでフィールドをコピーしたいだけです。

void operator=(const MyObject& v) {
  this->someField = v->someField;
}

// So this statement should generate just one function call and not a fancy 
// collection of temporaries that require construction destruction.
MyObject A = B;

どちらの場合も、コードははるかに高速に実行されます。2 番目のケースでも、効果は同様です。

3) また、型についてはどうですか... 演算子を使用して、型への他の型の割り当てを処理します。

void operator=(const MyObject& v) {
  this->someField = v->someField;
}
void operator=(int x) {
  this->value = x;
}
void operator=(float y) {
  this->floatValue = y;
}
于 2013-02-15T18:03:19.103 に答える
2

まず、「古いオブジェクトの破棄とそれに続く新しいオブジェクトのコピー構築」は例外安全ではないことに注意してください。

しかし、「新しいオブジェクトのコピー構築、その後の古いオブジェクトとのスワップ、それに続く古いオブジェクトの破棄」については、これが代入演算子を実装するためのスワップイディオムであり、正しく実行されれば例外セーフです。

場合によっては、カスタム代入演算子が swap イディオムよりも高速になることがあります。たとえば、POD 型の直接配列は、下位レベルの割り当てによる場合を除き、実際にはスワップできません。そのため、swap イディオムについては、配列サイズに比例するオーバーヘッドが予想されます。

ただし、歴史的には、スワッピングと例外の安全性はあまり重視されていませんでした。

bjarne はもともと (私の記憶が正しければ) 例外を望んでいましたが、1989 年かそこらになるまで言語に取り込まれませんでした。そのため、プログラミングの元の C++ の方法は、割り当てに重点を置いていました。失敗したコンストラクターが 0 を代入することによってその失敗を通知した程度までthis… 、当時はあなたの質問は意味がなかったと思います。それはただの割り当てでした。


タイプごとに、いくつかのオブジェクトにはアイデンティティがあり、他のオブジェクトには値があります。値オブジェクトに代入することは理にかなっていますが、ID オブジェクトの場合は、通常、オブジェクトを変更できる方法を制限したいと考えています。これにはコピーの割り当てをカスタマイズする機能は必要ありませんが (使用不可にするためだけに)、その機能があれば他の言語サポートは必要ありません。


考えられる他の特定の理由についても同様だと思います。おそらく、そのような理由で一般的な能力が実際に必要になることはありませんが、一般的な能力はすべてをカバーするのに十分であるため、言語全体の複雑さが低下します。


私の勘、回想、直感よりも決定的な答えを得るための良い情報源は、bjarne の「c++ の設計と進化」の本です。

おそらく、質問には決定的な答えがあります。

于 2013-02-15T18:03:33.800 に答える
2

実際、(削除された)juanchopanzaの回答を見た後、私は自分でそれを理解することになったと思います.

コピー代入演算子を使用すると、クラスがリソース(この場合はメモリ) を再利用できるときに リソースを不必要に割り当てることbasic_string避けることができます。

したがって、に代入するbasic_stringと、オーバーロードされたコピー代入演算子はメモリの割り当てを回避し、文字列データを既存のバッファーに直接コピーするだけです。

オブジェクトを破棄して再度構築する必要がある場合は、バッファーを再割り当てする必要があり、小さな文字列の場合はさらにコストがかかる可能性があります。

(vectorこれも恩恵を受ける可能性があることに注意してください。ただし、要素のコピー コンストラクターが決して例外をスローしないことがわかっている場合のみです。それ以外の場合は、例外の安全性を維持し、実際にコピー アンド スワップを実行する必要があります。)

于 2013-02-15T18:24:45.923 に答える
2

古いオブジェクトの破棄とそれに続く新しいオブジェクトのコピー構築は、通常は機能しません。また、クラスが特別な swap 関数を提供しない限り、swap イディオムは機能しないことが保証されています。つまりstd::swap、特殊化されていない実装で代入を使用し、代入演算子で直接使用すると、無限の再帰が発生します。

もちろん、ユーザーは何か特別なことをしたいと思うかもしれません。たとえば、代入演算子を非公開にするなどです。

そして最後に、ほぼ間違いなく決定的な理由は次のとおりです。デフォルトの代入演算子は C と互換性がなければなりません。

于 2013-02-15T18:05:24.940 に答える
1

他のタイプの代入も使用できます。ID を割り当てる代入演算子を持つ
クラスPersonを持つことができます。

しかし、それ以外に、常にすべてのメンバーをそのままコピーしたいとは限りません。

デフォルトの割り当ては浅いコピーのみを行います。
たとえば、クラスにポインターまたはロックが含まれている場合、それらを他のオブジェクトから常にコピーしたいとは限りません。
通常、ポインターがある場合は、ディープ コピーを使用し、ポインターが指しているオブジェクトのコピーを作成することがあります。
また、ロックがある場合は、それらをオブジェクトに固有のものにする必要があり、その状態を他のオブジェクトからコピーしたくありません。

クラスがメンバーとしてポインターを保持している場合、実際には独自のコピー コンストラクターと代入演算子を提供するのが一般的な方法です。

于 2013-02-15T18:02:50.880 に答える
0

私はそれを変換コンストラクターとして頻繁に使用しましたが、既存のオブジェクトと共に使用しました。つまり、メンバー変数の型などをオブジェクトに割り当てます。

于 2013-02-15T17:59:06.127 に答える