web-dev-qa-db-ja.com

キャストを避ける必要があるのはなぜですか?

一般的に、型のキャストはできるだけ避けます。これは、コーディングの習慣が悪く、パフォーマンスが低下する可能性があるという印象を受けているためです。

しかし、誰かが私にそれが正確になぜであるかを説明するように頼んだら、私はおそらくそれらをヘッドライトの鹿のように見るでしょう。

それで、なぜ/いつキャストが悪いのですか?

Java、c#、c ++で一般的ですか、それともすべての異なるランタイム環境が独自の用語でそれを処理しますか?

任意の言語の仕様を歓迎します。たとえば、C++でなぜ悪いのでしょうか。

94

これに3つの言語をタグ付けしましたが、3つの言語で答えはかなり異なります。 C++の議論は多かれ少なかれCキャストの議論を暗示し、それは(多かれ少なかれ)4番目の答えを与えます。

明示的に言及しなかったものなので、Cから始めます。Cキャストには多くの問題があります。 1つは、さまざまなことを行えることです。場合によっては、キャストは(本質的に)コンパイラーに伝えるだけのことです:「黙って、私がやっていることを知っています」-つまり、問題を引き起こす可能性のある変換を行う場合でも、これらの潜在的な問題については警告しません。たとえば、char a=(char)123456;。定義されたこの実装の正確な結果は(charのサイズと符号に依存します)、かなり奇妙な状況を除いて、おそらく役に立たないでしょう。 Cキャストは、コンパイル時にのみ発生するもの(つまり、一部のデータの解釈/処理方法をコンパイラに指示しているだけ)か、実行時に発生するもの(たとえば、実際のdoubleから長いです)。

C++は、それぞれがCキャストの機能のサブセットのみに制限されている「新しい」キャスト演算子をいくつか追加することにより、少なくともある程度までそれを処理しようとします。これにより、(たとえば)本当に意図していなかった変換を誤って実行するのが難しくなります-onlyにconstnessを捨てようとする場合オブジェクト、const_castを使用できます。そして、onlyが影響するのは、オブジェクトがconstかどうかです、volatile、またはそうではありません。逆に、static_castは、オブジェクトがconstまたはvolatileであるかどうかに影響を与えることはできません。要するに、ほとんど同じタイプの機能がありますが、それらは分類されているため、通常、1つのキャストは1種類の変換のみを実行できます。主な例外は、あなたがcan少なくともいくつかのケースで、書かれているにもかかわらず、dynamic_castの代わりにstatic_castを使用できることです。 dynamic_castとして、実際にはstatic_castになります。たとえば、dynamic_castを使用してクラス階層を上下に移動できますが、階層の「上への」キャストは常に安全であるため、階層の「下への」キャストは静的に実行できます。必ずしも安全なので、動的に行われます。

JavaとC#は互いに非常に似ています。特に、両方ともキャストは(事実上?)常に実行時の操作です。 C++キャスト演算子の観点では、実際に行われていることの点で通常dynamic_castに最も近いです。つまり、オブジェクトをあるターゲットタイプにキャストしようとすると、コンパイラは実行時チェックを挿入して確認しますその変換が許可されているかどうか、許可されていない場合は例外をスローします。正確な詳細(たとえば、「不正なキャスト」例外に使用される名前)は異なりますが、基本的な原則はほとんど同じままです(ただし、メモリが提供される場合、Javaはキャストを少数に適用しますCキャストに非常に近いintのような非オブジェクト型-しかし、これらの型はめったに使用されないので、1)確かに覚えていない、2)たとえそれが真実であっても問題ではないとにかく)。

より一般的に見ると、状況は非常に単純です(少なくともIMO):キャスト(明らかに十分)は、何かをあるタイプから別のタイプに変換していることを意味します。あなたがそうするとき/するなら、それは「なぜ」という質問を提起します。何かを特定のタイプにしたい場合、なぜそのタイプを最初から定義しないのですか?それはneverそのような変換を行う理由があると言うことではありませんが、それが起こるときはいつでも、それを再設計できるかどうかの質問を促すべきです正しいタイプが全体で使用されたコード。一見無害に見える変換(たとえば、整数と浮動小数点の間)でさえ、一般的な方法よりもはるかに綿密に調べる必要があります。 seemingの類似性にもかかわらず、整数は、実際には「カウントされた」タイプの事物に、浮動小数点は「測定された」事物に使用されるべきです。この区別を無視することは、「平均的なアメリカ人家族には1.8人の子供がいる」といった狂気の声明の一部につながるものです。それがどのように発生するかはすべて確認できますが、事実はno家族には1.8人の子供がいます。彼らは1を持っているかもしれないし、2を持っているかもしれないし、それ以上を持っているかもしれない-しかし決して1.8ではない。

135
Jerry Coffin

ここにはたくさんの良い答えがあります。私がそれを見る方法は次のとおりです(C#の観点から)。

通常、キャストとは次の2つのいずれかを意味します。

  • この式の実行時の型は知っていますが、コンパイラはそれを知りません。コンパイラ、実行時に、この式に対応するオブジェクトは実際にこのタイプになります。今のところ、この式はこのタイプのものとして扱われることを知っています。オブジェクトが指定された型であると想定するコードを生成するか、間違っている場合は例外をスローします。

  • コンパイラーと開発者の両方が、式のランタイム型を知っています。実行時にこの式が持つ値に関連付けられた異なるタイプの別の値があります。指定された型の値から目的の型の値を生成するコードを生成します。できない場合は、例外をスローします。

それらは反対であることに注意してください。キャストには2種類あります! hintをコンパイラにrealityについて与えるキャストがあります-ちょっと、このオブジェクト型は実際にはCustomer型です-そして、ある型から別の型へのマッピングを実行するようコンパイラーに指示しているキャストがあります-ちょっと、このdoubleに対応するintが必要です。

どちらのキャストも危険です。最初の種類のキャストでは、「なぜ開発者はコンパイラーが知らないことを知っているのか」という疑問が生じます。そのような状況にある場合、通常は、コンパイラdoesが現実に対処できるようにプログラムを変更することをお勧めします。その場合、キャストは必要ありません。分析はコンパイル時に行われます。

2番目の種類のキャストでは、「そもそもターゲットデータ型で操作が行われないのはなぜですか?」という疑問が生じます。 intで結果が必要な場合、なぜ最初にダブルを持っているのですか? intを保持してはいけませんか?

ここでいくつかの追加の考え:

http://blogs.msdn.com/b/ericlippert/archive/tags/cast+operator/

46
Eric Lippert

キャストエラーは、Javaの実行時エラーとして常に報告されます。ジェネリックまたはテンプレートを使用すると、これらのエラーがコンパイル時エラーに変わり、ミスを検出するのがはるかに簡単になります。

上で言ったように。これは、すべてのキャスティングが悪いということではありません。しかし、それを回避することが可能であれば、そうすることが最善です。

35
Mike Miller

キャストは本質的に悪いものではありません。それは、実際にはまったく実行すべきでない、またはよりエレガントに実行すべきことを達成するための手段としてしばしば誤用されているということです。

それが普遍的に悪い場合、言語はそれをサポートしません。他の言語機能と同様に、その場所があります。

私のアドバイスは、あなたの第一言語に焦点を合わせ、そのすべてのキャストと関連するベストプラクティスを理解することです。それは他の言語への遠足を知らせるはずです。

関連するC#ドキュメントは here です。

C++オプションに関する素晴らしい要約があります 以前のSOここに質問

18
Steve Townsend

私は主にC++について話しますが、これのほとんどはおそらくJava =およびC#も:

C++は静的に型付けされた言語です。これには言語が許可するいくつかの余裕があります(仮想関数、暗黙的な変換)が、基本的にコンパイラーはコンパイル時にすべてのオブジェクトのタイプを知っています。このような言語を使用する理由は、エラーがコンパイル時にキャッチされる可能性があるためです。コンパイラがabの型を知っている場合、a=bを実行するとコンパイル時にキャッチされます。ここで、aは複素数で、 bは文字列です。

あなたはもっとよく知っているだと思うので、明示的なキャストを行うときはいつでも、コンパイラにシャットダウンするように伝えます。間違っている場合は、通常、at run-timeのみがわかります。そして、実行時に発見することの問題は、これが顧客のものであるかもしれないということです。

9
sbi

Java、c#、およびc ++は強く型付けされた言語ですが、強く型付けされた言語は柔軟性に欠けると見なすことができますが、コンパイル時に型チェックを行うという利点があり、特定の操作で誤った型を使用することによって引き起こされる実行時エラーから保護します。

基本的に2種類のキャストがあります。より一般的なタイプへのキャストまたは他のタイプ(より具体的な)へのキャストです。より一般的な型へのキャスト(親型へのキャスト)では、コンパイル時のチェックはそのまま残ります。ただし、他の型(より具体的な型)にキャストすると、コンパイル時の型チェックが無効になり、ランタイムチェックによってコンパイラーに置き換えられます。これは、コンパイルされたコードが正しく実行されるかどうかの確実性が低いことを意味します。また、追加のランタイム型チェックにより、パフォーマンスへの影響は無視できます(Java APIはキャストでいっぱいです!)。

5
Kdeveloper

一部のタイプの鋳造は非常に安全で効率的であるため、鋳造とはみなされないこともよくあります。

派生型から基本型にキャストする場合、これは一般的に非常に安く(多くの場合、言語、実装、およびその他の要因に応じて-ゼロコストです)、安全です。

Intのような単純な型からlong intのような幅の広い型にキャストする場合、これもまた非常に安価であることが多く(通常、そのキャストと同じ型を割り当てるよりもそれほど高価ではありません)、再び安全です。

他のタイプは、より複雑で高価です。ほとんどの言語では、ベース型から派生型へのキャストは安価ですが、重大なエラーのリスクが高くなります(C++では、ベースから派生へのstatic_castを行うと安価になりますが、基礎となる値が派生型ではない場合、動作は未定義であり、非常に奇妙な場合があります)または比較的高価で、例外(C++のdynamic_cast、C#の明示的なベースから派生キャストなど)が発生するリスクがあります。 JavaおよびC#がこの例の別の例であり、さらに大きな出費があります(基礎となる値の処理方法以上に変化していると考えてください)。

他のタイプのキャストは、情報を失う可能性があります(長整数型から短整数型)。

これらのリスク(例外またはより重大なエラー)および費用のケースはすべて、キャストを回避する理由です。

より概念的ですが、おそらくより重要な理由は、キャストの各ケースは、コードの正確性について推論する能力が妨げられるケースであるということです。各ケースは、何かがうまくいかない別の場所であり、その方法システム全体がうまくいかないかどうかを推測する複雑さを増すことになります。キャストが毎回証明的に安全であるとしても、これを証明することは推論の余分な部分です。

最後に、キャストを頻繁に使用すると、オブジェクトモデルの作成時、使用時、またはその両方でオブジェクトモデルを適切に検討できないことが示されます。同じ少数のタイプ間で頻繁に前後にキャストすることは、ほとんどの場合、タイプ中古。ここで、キャストが悪いのはそれほど悪いことではありません。キャストは何か悪い兆候だからです。

4
Jon Hanna

プログラマーは、言語機能の使用に関する独断的なルール(「XXXを使用しない!」、「有害と見なされるXXX」など)に固執する傾向が高まっています。XXXはgotosからprotectedデータメンバーをシングルトンに渡し、値でオブジェクトを渡します。

私の経験では、このようなルールに従うことで、次の2つのことが保証されます。

より良いアプローチは、これらの包括的な禁止の背後にある真実の核を掘り下げて明らかにし、それらの状況を /多くの状況があることを理解して、慎重に機能を使用することです仕事に最適なツール。

「型のキャストはできる限り避けます」は、そのような一般化されたルールの良い例です。キャストは多くの一般的な状況で不可欠です。いくつかの例:

  1. サードパーティのコードと相互運用する場合(特にそのコードにtypedefsが含まれる場合)。 (例:GLfloat <-> double <-> Real。)
  2. 派生クラスから基本クラスへのポインター/参照からのキャスト:これは非常に一般的で自然なため、コンパイラーが暗黙的に実行します。明示的にすると読みやすさが向上する場合、キャストは後方ではなく前方へのステップです!
  3. ベースから派生クラスポインター/参照へのキャスト:よく設計されたコードでも共通です。 (例:異種コンテナー。)
  4. 組み込み型の生バイトへのアクセスを必要とするバイナリシリアル化/逆シリアル化またはその他の低レベルコード内。
  5. 別のタイプを使用するほうが、より自然で便利で読みやすい場合はいつでも。 (例:std::size_type-> int。)

確かに、キャストを使用するのが適切なnotである多くの状況があり、これらを学ぶことも重要です。上記の答えはそれらのいくつかを指摘するのに良い仕事をしたので、私はあまり詳細には行きません。

2
user168715

2つの条件が満たされている場合にのみ、オブジェクトを特定のタイプにキャストします。

  1. あなたはそれがそのタイプであることを知っています
  2. コンパイラは

これは、使用するすべての情報が、使用する型構造で適切に表現されているわけではないことを意味します。実装はモデルを意味的にで構成する必要があるため、これは悪いことですが、この場合は明らかにそうではありません。

キャストを行うとき、これには2つの異なる理由があります。

  1. 型の関係を表現するのに悪い仕事をしました。
  2. 言語型システムは単に表現するだけの表現力がない。

ほとんどの言語では、何度も2番目の状況に遭遇します。 JavaのようなジェネリックはC++テンプレートシステムを少し助けますが、マスターするのは難しく、それでもいくつかのことは不可能であるか、努力するだけの価値がないかもしれません。

キャストは、特定の言語で特定の型の関係を表現するために問題を回避するための汚いハックです。汚いハッキングは避けてください。しかし、あなたは彼らなしでは生きていけません。

1
back2dos

誰かがすでにこれについて言及しているかどうかはわかりませんが、C#キャストではかなり安全な方法で使用でき、しばしば必要になります。いくつかのタイプのオブジェクトを受け取るとします。 isキーワードを使用すると、最初にオブジェクトが実際にキャストしようとしているタイプであることを確認してから、オブジェクトをそのタイプに直接キャストできます。 (私はJavaで作業しませんでしたが、そこにそれを行う非常に簡単な方法もあると確信しています)。

1
user472875

KDeveloperの答え を詳しく説明すると、本質的にタイプセーフではありません。キャストでは、キャスト元とキャスト先が一致するという保証はありません。一致すると、実行時例外が発生しますが、これは常に悪いことです。

特にC#に関しては、 is および as 演算子が含まれているため、(ほとんどの場合)機会があります。キャストが成功するかどうかを判断します。このため、適切な手順を実行して、操作が成功するかどうかを判断し、適切に続行する必要があります。

1
casperOne

C#の場合、値型の処理中にボックス化/ボックス化解除のオーバーヘッドが発生するため、キャスト中はより注意する必要があります。

1

通常、テンプレート(またはジェネリック)はキャストよりも型安全です。その点で、キャスティングの問題は型安全性だと思います。ただし、特にダウンキャスティングに関連するもう1つの微妙な問題があります。それは設計です。少なくとも私の観点からすると、ダウンキャストはコードの匂いであり、私のデザインに何か問題があるかもしれないという兆候であり、さらに調査する必要があります。単純な理由:抽象化を正しく「取得」する場合、単にそれを必要としないだけです!いい質問ですね...

乾杯!

0

本当に簡潔にするために、正当な理由は移植性のためです。両方が同じ言語に対応する異なるアーキテクチャには、たとえばサイズの異なるintが含まれる場合があります。したがって、ArchAからより狭いintを持つArchBに移行すると、せいぜい奇妙な動作が見られ、最悪の場合はセグフォールトが発生する可能性があります。

(私は明らかに、アーキテクチャに依存しないバイトコードとILを無視しています。)

0
kmarks2