Scalaの演算子が「良い」のに、C ++の「悪い」のオーバーロードが発生するのはなぜですか。


155

C ++での演算子のオーバーロードは、多くの人にとってA Bad Thing(tm)であると考えられており、新しい言語ではミスを繰り返さないでください。確かに、これはJavaの設計時に特に削除された機能の1つでした。

Scalaについて読み始めたところ、演算子のオーバーロードに非常によく似ていることがわかりました(ただし、技術的には演算子がないため、関数だけなので、演算子のオーバーロードはありません)。ただし、C ++での演算子のオーバーロードとは質的に異なるようには思われません。C++では、演算子は特別な関数として定義されています。

それで私の質問は、Scalaで "+"を定義するアイデアがC ++でのアイデアよりも優れている理由は何ですか?


27
C ++でもScalaでも、すべてのプログラマーの間の普遍的な合意によって定義されたものではありません。一部の人々がC ++についてむしゃむしゃしているという事実と、Scalaについてむずかしいという人がいるという事実の間に矛盾はないと思います。
スティーブジェソップ

16
C ++での演算子のオーバーロードに問題はありません。
パピー

5
これは新しいことではありませんが、演算子のオーバーロードやその他の「高度な」機能が呼び出されたときにC ++を防御する方法は簡単です。私は、有能で自律的であると想定され、このような決断をする必要がない方法を常に気に入っていました。
エリオット

Scalaは、c ++の後の数十年のように設計されました。その背後にある人はプログラミング言語の面で超賢明であることがわかります。それ自体悪いことは何もありません。もしあなたがc ++またはScalaにさらに100年間固執すれば、おそらく両方が悪いことは明らかです!偏見は明らかに私たちの本質にありますが、それと戦うことができます。テクノロジーの歴史を見るだけで、すべてが時代遅れになります。
Nader Ghanbari、2017年

回答:


242

C ++はCからtrue blue演算子を継承します。つまり、6 + 4の「+」は非常に特殊です。たとえば、その+関数へのポインタを取得することはできません。

一方、Scalaにはそのような演算子はありません。メソッド名の定義に非常に柔軟性があるだけでなく、単語以外の記号の組み込み優先順位が少しあります。したがって、技術的にはScalaには演算子のオーバーロードはありません。

何を呼び出しても、C ++であっても、演算子のオーバーロードは本質的に悪いことではありません。問題は、悪いプログラマがそれを乱用するときです。しかし率直に言って、私は、プログラマーがオペレーターのオーバーロードを悪用する能力を取り除いても、プログラマーが悪用する可能性のあるすべてのものを修正するというバケツを落とすことはないと考えています。本当の答えはメンタリングです。 http://james-iry.blogspot.com/2009/03/operator-overloading-ad-absurdum.html

それにもかかわらず、C ++の演算子のオーバーロードとScalaの柔軟なメソッドの名前付けには違いがあります。これにより、ImhoはScalaの乱用を少なくし、乱用しやすくなります。

C ++では、インフィックス表記を取得する唯一の方法は、演算子を使用することです。それ以外の場合は、object.message(argument)またはpointer-> messsage(argument)またはfunction(argument1、argument2)を使用する必要があります。したがって、コードに特定のDSL風のスタイルが必要な場合は、演算子を使用する必要があります。

Scalaでは、任意のメッセージ送信でインフィックス表記を取得できます。「オブジェクトメッセージ引数」は完全に問題ありません。つまり、中置表記を取得するためだけに非単語記号を使用する必要はありません。

C ++演算子のオーバーロードは、基本的にC演算子に制限されています。「+」や「>>」などの比較的少数の記号に関連のない広範な概念をマッピングしようとするように人々に圧力をかける演算子のみを使用できるという制限と組み合わせる

Scalaでは、メソッド名として、単語以外の有効な記号を幅広く使用できます。たとえば、私はあなたが書くことができる組み込みPrologっぽいDSLを持っています

female('jane)!         // jane is female
parent('jane,'john)!   // jane is john's parent
parent('jane, 'wendy)! // jane is wendy's parent

mother('Mother, 'Child) :- parent('Mother, 'Child) & female('Mother) //'// a mother of a child is the child's parent and is female

mother('X, 'john)?  // find john's mother
mother('jane, 'X)?  // find's all of jane's children

:-、!、?、および&記号は、通常のメソッドとして定義されています。C ++のみで&は有効であるため、このDSLをC ++にマップするには、すでに非常に異なる概念を呼び起こすいくつかのシンボルが必要です。

もちろん、これはScalaを別の種類の虐待にさらします。Scalaでは、必要に応じてメソッドに$!&^%という名前を付けることができます。

Scalaのように、非単語関数とメソッド名の使用に柔軟性がある他の言語については、Smalltalkを参照してください。Scalaのように、すべての「演算子」は単なる別のメソッドであり、プログラマーが柔軟な名前の優先順位と固定性を定義できるようにするHaskellです。関数。


最後に確認したところ、3.operator +(5)が機能しました。&(3.operator +)がそうでないことに本当に驚いています。
ジョシュア

たとえば、c ++でassert(female( "jane"))を実行できます。それはまったく混乱しないでしょう-james-iryの投稿に戻ってうなずいて、operator +が悪いことではないということですが、愚かなプログラマはそうです。
pm100

1
@Joshuaのint main() {return (3).operator+(5);}結果error: request for member ‘operator+’ in ‘3’, which is of non-class type ‘int’
zildjohn01

それは傲慢ながらくたの束です:「C ++であっても、オペレーターのオーバーロードは本質的に悪いわけではありません。問題は、プログラマーが悪用することです。」何かが簡単に悪用され、それを使用してもほとんどメリットがない場合、全体的な結果として、コードを保守している次の人物がコードの奇妙な部分を解読する際の生産性が失われます。それ以外の場合:非常に有益でよく書かれた回答。
Jukka Dahlbom 2014

@JukkaDahlbomスマートポインターの存在は、それ自体で大きなメリットになります。そしてラムダ、ユーザー定義の数値タイプ、間隔タイプ...
Alexey Romanov

66

C ++での演算子のオーバーロードは、多くの人が悪いことだと考えています(tm)

無知のみによって。これはC ++のような言語では絶対に必要であり、「純粋」な見方から始まった他の言語は、設計者がその必要性を理解すると、それを追加したことは注目に値します。


30
私は実際にニールに同意します。変数/定数/オブジェクト/インスタンスを代数的実体として提示し、人々にそれらの相互作用を数学的な方法で理解させる場合、演算子のオーバーロードは不可欠です-これはプログラミングがIMHOを機能させる方法であるべきです。
マッサ

16
+ 1、C ++での演算子のオーバーロードは適切です。例えば、それはベクトル数学をよりきれいにします。多くのC ++機能と同様に、パワーは慎重に使用する必要があります。
ジョンスミス

7
@Kristo C ++は、割り当ておよびコピーが必要な値を使用するため。これを制御する必要があるため、少なくとも、特定のタイプの割り当て演算子を指定できる必要があります。

7
@Kristo:C ++の目的の1つは、ユーザー定義型が組み込み型と同じことをすべて実行できるようにすることです(ただし、暗黙的な変換などのコンテキストでは扱いが異なります)。27ビット整数を実装したい場合は、実装できます。これを使用すると、intを使用するのと同じようになります。演算子のオーバーロードなしでは、組み込み型と同じ構文でUDTを使用することはできません。そのため、結果の言語はこの意味で「C ++のように」なりません。
スティーブジェソップ

8
「その方法は狂気である」-さらに悪いことに、その方法はstd :: vector <bool>です!
スティーブジェソップ

42

演算子のオーバーロードは、C ++では一般的に悪い考えであると決して考えられていませんでした。単に演算子のオーバーロードの悪用は悪い考えであると考えられていました。いずれにせよ、より詳細な関数呼び出しでシミュレートできるため、言語でのオペレーターのオーバーロードは実際には必要ありません。Javaでのオペレーターのオーバーロードを回避することで、Javaの実装と仕様が少し単純になり、プログラマーがオペレーターを乱用しないようになりました。演算子のオーバーロードの導入について、Javaコミュニティではいくつかの議論がありました。

Scalaでの演算子のオーバーロードの利点と欠点はC ++と同じです。演算子のオーバーロードを適切に使用すれば、より自然なコードを記述できます。使用しない場合は、より暗号化された難読化されたコードを記述できます。

参考:演算子はC ++では特別な関数として定義されていません。演算子は他の関数と同じように動作します。ただし、名前の検索、メンバー関数である必要があるかどうか、および2つの方法で呼び出すことができるという事実は異なります。1 )演算子の構文、および2)operator-function-id構文。


「いずれにせよ、より詳細な関数呼び出しでシミュレートできるため、言語でのオペレーターのオーバーロードは実際には必要ありません。」そのロジックの下では、演算子は実際には必要ありません。なぜ使用しないのadd(2, multiply(5, 3))ですか?
Joe Z.

これは、通常使用される表記法と一致する場合です。数学者や物理学者を考慮してください。彼らは、演算子のオーバーロードをはるかに簡単に提供するC ++ライブラリを理解して使用できます。彼らはプログラミング言語よりも方程式に集中したいのです。
Phil Wright

19

この記事「C ++とJavaのポジティブレガシー」は、質問に直接答えます。

「C ++にはスタック割り当てとヒープ割り当ての両方があり、すべての状況を処理してメモリリークを引き起こさないように、オペレーターをオーバーロードする必要があります。確かに困難です。しかし、Javaには単一のストレージ割り当てメカニズムとガベージコレクターがあり、オペレーターのオーバーロードを簡単にします。」 ..

作者によると、JavaはC ++で複雑なため、誤って演算子のオーバーロードを省略しましたが、その理由を忘れていました(またはJavaに適用されないことを理解していませんでした)。

ありがたいことに、Scalaのような高水準言語は、同じJVMで実行しながら開発者に選択肢を提供します。


14
Eckelは、C ++の複雑さのためにオペレーターのオーバーロードがJavaから切り離されたと私がこれまでに見た唯一のソースであり、彼のソースは何であるかは述べていません。値引きします。私が言った他のすべての情報源は、潜在的な乱用のために捨てられたと私は言いました。gotw.ca/publications/c_family_interview.htmおよびnewt.com/wohler/articles/james-gosling-ramblings-1.htmlを参照してください。それらを「演算子の過負荷」でページ検索するだけです。
James Iry

9

演算子のオーバーロードに問題はありません。実際、数値型の演算子のオーバーロードがないことには何か問題があります。(BigIntegerとBigDecimalを使用するJavaコードを見てください。)

ただし、C ++にはその機能を悪用する伝統があります。よく引用される例は、ビットシフト演算子がI / Oを実行するためにオーバーロードされていることです。


<<と>>演算子は、転送の方法を視覚的に示しており、I / Oを実行すること意図しており、乱用ではなく、標準ライブラリと実用的なものからのものです。「cin >>何か」を見るだけで、どこに行くの?シンから何かへ、明らかに。
peenut 2012

7
@peenut:しかし、元々の用途はビットシフトでした。「標準ライブラリ」は、元の定義を完全に混乱させる方法で演算子を使用します。
Joe Z.

1
Bjarne Stroustrup(C ++の作成者)がC ++の=代わりに、<<および>>初期の頃にを使用して実験したことをどこかで読んだことは確かですが、適切な演算子の優先順位がないため問題が発生しました(つまり、左側または右側の引数)。そのため、彼の手は彼が使用できるものに少し縛られていました。
Phil Wright

8

一般的にそれは悪いことではありません。
C#などの新しい言語にも演算子のオーバーロードがあります。

悪いことは、オペレーターのオーバーロードの悪用です。

しかし、C ++で定義されている演算子のオーバーロードにも問題があります。オーバーロードされた演算子はメソッド呼び出しの単なる構文上の糖なので、メソッドと同じように動作します。一方、通常の組み込み演算子はメソッドのように動作しません。これらの不整合は問題を引き起こす可能性があります。

頭の上のオペレーター||&&
これらの組み込みバージョンはショートカット演算子です。これはオーバーロードされたバージョンには当てはまらず、いくつかの問題を引き起こしています。

+-* /すべてが操作対象と同じタイプを返すという事実(オペレーターの昇格後)
オーバーロードされたバージョンは何でも返すことができます(これは、乱用が発生した場所であり、オペレーターがユーザーが予期していないアービトレータータイプを返し始めた場合物事は丘を下ります)。


8

演算子のオーバーロードは、あなたが本当に頻繁に「必要」とするものではありませんが、Javaを使用しているときに、本当に必要なところに到達した場合は、タイピングをやめる言い訳をするために、指の爪を引き離したくなるでしょう。 。

あなたが見つけたそのコードは長いオーバーフローですか?うん、BigIntegerで動作させるには、全体を再入力する必要があります。変数の型を変更するためだけにホイールを再発明しなければならないことほどイライラすることはありません。


6

ガイスティール氏は、オペレーターのオーバーロードもJavaで行うべきだと主張し、基調講演「Growing a language」でビデオとその書き起こしがあり、それは本当に素晴らしいスピーチです。彼が最初の数ページについて何を話しているのか不思議に思うかもしれませんが、読み続けると、そのポイントがわかり、悟りが開かれます。そして、彼がそのようなスピーチをまったく行うことができたという事実も驚くべきことです。

同時に、この講演は、おそらくScalaを含む多くの基礎研究に影響を与えました。これは、現場で働くために誰もが読むべき論文の1つです。

ちなみに、彼の例は主に数値クラス(BigIntegerなどの奇妙なもの)に関するものですが、それは必須ではありません。

ただし、演​​算子のオーバーロードの誤用はひどい結果を招く可能性があり、使用するライブラリを少しも検討せずにコードを読み込もうとすると、適切な使用法でも問題が複雑になる可能性があることは事実です。しかし、それは良い考えでしょうか?OTOH、そのようなライブラリーは、オペレーターのチートシートをオペレーターに含めようとすべきではありませんか?


4

私はすべての答えがこれを逃したと信じています。C ++では、演算子を必要なだけオーバーロードできますが、演算子を評価する優先順位を変更することはできません。IIRC、Scalaにはこの問題はありません。

優先順位の問題に加えて、それが悪い考えであることに関して、人々はオペレーターにとって本当に意味のない意味を思いつき、それが読みやすさを助けることはめったにありません。Scalaライブラリーは、特にこれを嫌いです。毎回覚えなければならない間抜けなシンボルであり、ライブラリーのメンテナーは「一度学ぶだけでよい」と言って砂に頭を抱えています。すばらしい、今私はいくつかの '賢い'作者の不可解な構文*使用したいライブラリの数を学ぶ必要があります。演算子の読み書き可能なバージョンを常に提供する規則が存在していたとしても、それほど悪くはありません。


1
Scalaには演算子の優先順位も固定されていますね。
skaffman 2009

あると思いますが、それははるかに平坦です。さらに言えば、Scalaのオペレーター期間は少なくなっています。+、-、*はメソッドであり、演算子ではありません。IIRCです。2 + 3 * 2は、8ない理由では、それは10だ
SAEM

7
Scalaには、シンボルの最初の文字に基づく優先順位システムがあります。scala> 2 + 3 * 2 res0:Int = 8
James Iry

3

オペレーターのオーバーロードはC ++の発明ではありませんでした-それはアルゴールIIRCから来たものであり、ゴスリングでさえそれが一般的に悪い考えであると主張していません。


確かに、しかしそれはそのC ++の化身の中にあり、それが一般に不評の雰囲気を得た。
skaffman 2009

5
「評判の悪い空気」とはどういう意味ですか?私が知っているほとんどの人は、オペレーターのオーバーロードをサポートする言語(C ++、C#)を使用しており、不満を聞いたことはありません。
Nemanja Trifunovic

私はANSI以前のC ++での長年の経験から話していますが、確かにそれらの一般的な嫌いを思い出します。おそらく、状況はANSI C ++で改善されたか、人々はそれを乱用しない方法を学んだだけでしょう。
skaffman 2009

1
cfrontの日(80代半ば)からC ++を使用している人物と言えば、ISO規格の導入が、オペレーターの過負荷に関する人々の偏見に影響を与えなかったことは間違いありません。

3

C ++で間違っていることがわかっている唯一のことは、個別の演算子として[] =をオーバーロードする機能がないことです。これはおそらく明白な理由ではないが、それだけの価値があるため、C ++コンパイラに実装するのは難しいかもしれません。


2

他の答えが指摘したように; 演算子自体のオーバーロードは必ずしも悪いことではありません。結果のコードがわかりにくくなるような方法で使用すると何が悪いのでしょうか。一般的に、それらを使用するときは、最も驚くべきことを行わないようにする必要があります(operator + do除算を使用すると、有理クラスの使用で問題が発生します)、またはScott Meyersが言うように:

クライアントはintのような型がどのように動作するかをすでに知っているので、妥当な場合は常に同じように型が動作するように努力する必要があります... 疑わしい場合は、intと同じようにしてください。(Effective C ++ 3rd Edition item 18から)

現在、一部の人々は、boost :: spiritのようなものでオペレーターの過負荷を極端にしています。このレベルでは、それがどのように実装されるかはわかりませんが、やりたいことを実行するための興味深い構文になります。これが良いのか悪いのかわかりません。いい感じですが、使ったことはありません。


ここで演算子のオーバーロードについて賛成したり反対したりはしていません。正当化する人を探していません。
skaffman 2009

Sprintは、私が遭遇した最悪の例の近くにはありません。RogueWaveデータベースライブラリの最新情報がわかるはずです。

スピリットがオペレーターを誤用していることに同意しますが、それを行うためのより良い方法を実際に考えることはできません。
Zifre 2009

1
精神がオペレーターをかなり乱用しているとは思わないが、それはそれを押し進めている。それを行う方法は他にないことに同意します。基本的に、C ++の構文内でDSLを作成します。C ++が行うように設計されたものからはかなりかけ離れています。はい、もっと悪い例があります:)一般に、私は適切な場合にそれらを使用します。ほとんどの場合、簡単なデバッグ\ロギングのためのストリーミングオペレーターのみです。そして、クラスに実装されたメソッドに転送されるのは、砂糖だけです。
マット価格

1
それは好みの質問です。ただし、関数型言語のパーサーコンビネーターライブラリは、Spiritと非常によく似た方法で演算子をオーバーロードします。技術的理由には多くの理由があります。Googleは、「埋め込みドメイン固有言語」については一般的な観点から説明した多くの論文を見つけ、Googleは「スカラパーサーコンビネーター」についてはこの場合の実用的な例を示します。関数型言語では、結果の構文の方が優れていることがよくあります。たとえば、パーサーを連結するために>>の意味を変更する必要はありません。
Blaisorblade、2010年

2

C ++の演算子のオーバーロードが悪いと主張する記事を見たことがありません。

ユーザー定義可能な演算子を使用すると、言語のユーザーがより高いレベルの表現力と使いやすさを実現できます。


1

ただし、C ++での演算子のオーバーロードとは質的に異なるようには思われません。C++では、演算子は特別な関数として定義されています。

AFAIK、「通常の」メンバー関数と比較して、演算子関数には特別なものはありません。もちろん、オーバーロードできる特定の演算子のセットしかありませんが、それはそれらを特別なものにするわけではありません。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.