キャストを避ける必要があるのはなぜですか?[閉まっている]


97

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

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

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

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

任意の言語の詳細は大歓迎です。たとえば、C ++ではなぜ悪いのですか?


3
どこでこの印象を得ましたか?
2010年

8
本を読んだことも、「CASTING SOOOO GOOOOD !!!」と言ったプログラマーに会ったことがないので、この印象を受けました。
10

15
C ++の答えは、必然的にC#の答えとは異なります。これは非常に言語固有です。これまでの回答は、特定の言語についてこの質問に回答しており、場合によっては、彼らが話している言語を述べていません。
James McNellis、2010年

2
悪いとは相対的な言葉です。キャストを回避することはベストプラクティスですが、プログラマーはプログラマーがしなければならないことを行わなければならない場合があります。(特に、1.4用に作成されたライブラリーを使用するJava 1.5+プログラムを作成している場合)おそらく「キャストを回避する必要があるのはなぜですか?」という質問の名前を変更します。
Mike Miller

2
これは素晴らしい質問です...評判がそれぞれ1万人未満の5人のユーザーによってクローズされました!! 明らかに、新しい力の熱狂的な使用。再開に投票しました。
Reach4thelasers

回答:


141

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

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

C ++は、いくつかの「新しい」キャスト演算子を追加することにより、少なくともある程度はそれに対処しようとします。それぞれの演算子は、Cキャストの機能のサブセットのみに制限されています。あなたがいる場合-これが(たとえば)偶然あなたが本当に意図していなかった変換を行うにはそれがより困難にのみ、あなたは使用することができ、オブジェクトのconst性を離れてキャストする予定をconst_castし、必ずということだけで、それが影響を与えることができるものがいるかどうかでありますオブジェクトがあるconstvolatileまたはありません。逆に、static_castオブジェクトがあるかどうかに影響することが許可されていませんconstか、volatile。つまり、ほとんど同じタイプの機能を持っていますが、それらは分類されているため、1つのキャストでは通常1種類の変換しか実行できません。1つのCスタイルのキャストでは、1回の操作で2つまたは3つの変換を実行できます。主な例外は、少なくとも一部のケースでの代わりにを使用できることで、として記述されていても、実際にはとして終了します。たとえば、クラス階層を上下にトラバースするために使用できますが、階層を「上に」キャストすると常に安全であるため、静的に実行できますが、階層を「下に」キャストすると必ずしも安全ではないため、動的に行われます。dynamic_caststatic_castdynamic_caststatic_castdynamic_cast

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

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


10
ここで「注意による死」に苦しんでいるように見えます。これはいい答えです。
スティーブタウンゼント

2
強力な答えですが、「わからない」部分のいくつかは、包括的にするために引き締められる可能性があります。@ Dragontamer5788それは良い答えですが、包括的ではありません。
M2tM 2010年

5
全体の子供1人と足のない子供1人は1.8人の子供です。しかし、それでも非常に良い答えです。:)
オズ。

1
子供がいないカップルを除外することによって、少なくとも妨げられない非常に良い答え。

@ロジャー:除外せず、単に無視します。
Jerry Coffin、

48

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

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

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

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

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

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

2番目の種類のキャストは、「なぜ最初にターゲットのデータ型で操作が行われないのか」という疑問を投げかけます。intで結果が必要な場合、なぜ最初にdoubleを保持しているのですか?あなたはintを保持すべきではありませんか?

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

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


2
私はそれらが本質的に赤旗であるとは思わない。Fooから継承するオブジェクトがありBar、それをに格納するList<Bar>場合、それをFoo元に戻すにはキャストが必要になります。おそらくそれは、アーキテクチャーレベルの問題(なぜBarsではなくs を格納するFooのか)を示していますが、必ずしもそうではありません。また、それFooがへの有効なキャストを持っている場合は、int他のコメントも処理します。は必ずしも適切ではないため、を格納するのFooではなく、を格納します。intint
Mike Caron

6
@Mike Caron-私は明らかにエリックに答えることはできませんが、私にとって赤旗は「これは何か考えていること」を意味し、「これは何か間違っている」ではありません。そして、をFooに格納することに問題はありませんList<Bar>が、キャストの時点で、にFoo適していない何かを実行しようとしていますBar。つまり、サブタイプの異なる動作は、仮想メソッドによって提供される組み込みのポリモーフィズム以外のメカニズムを介して行われています。多分それは正しい解決策ですが、多くの場合それは危険信号です。
Jeffrey L Whitledge、2010年

13
確かに。動物のリストから物を引っ張っているときに、後でコンパイラーに伝える必要がある場合、ちなみに、1つ目はトラ、2つ目はライオン、3つ目はライオンであることを知っています。クマの場合、List <Animal>ではなく、Tuple <Lion、Tiger、Bear>を使用しているはずです。
Eric Lippert、2010年

5
私はあなたに同意しますが、Tuple<X,Y,...>タプルのより良い構文サポートがなければ、C#で広く使用される可能性は低いと思います。これは、言語が人々を「成功の穴」へと押し上げるより良い仕事をすることができる一つの場所です。
kvb 2010年

4
@kvb:同意します。C#4のタプル構文の採用を検討しましたが、予算に収まりませんでした。おそらくC#5で。完全な機能セットはまだ完成していません。非同期のためにCTPをまとめるのが忙しい。または、おそらく架空の将来のバージョンで。
Eric Lippert、2010年

36

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

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


4
これは、すべてのキャストが「悪い」と想定しています。ただし、これは完全には当てはまりません。たとえば、暗黙のキャストと明示的なキャストの両方をサポートするC#を取り上げます。問題は、(誤って)情報またはタイプセーフ(これは言語によって異なります)を削除するキャストが実行されるときに発生します。

5
実際、これはC ++では完全に間違っています。おそらく、この情報の対象となる言語を含めるための編集が適切です。
Steve Townsend

@エリック-そうですが、Javaについての最初のことも、情報が正しいことを確認するのに十分なC#の詳細もわかりません。
スティーブタウンゼンド

申し訳ありませんが、私はJavaの人なので、私のc ++の知識は十分に限られています。「テンプレート化」という単語は、あいまいにすることを意図していたため、編集することができました。
Mike Miller、

そうではありません。いくつかのキャストエラーは、一般的なエラーだけでなく、Javaコンパイラによってキャッチされます。(String)0を検討してください。
ローン侯爵

18

キャスティングは本質的に悪いことではありません。それは、実際にはまったく行われないか、よりエレガントに行われるべきではないことを達成する手段として誤用されることが多いだけです。

もしそれが全体的に悪いのなら、言語はそれをサポートしないでしょう。他の言語機能と同様に、その場所があります。

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

関連するC#ドキュメントはこちらです。

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


9

ここでは主にC ++について話していますが、これのほとんどはおそらくJavaとC#にも当てはまります。

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

明示的なキャストを行うときはいつでも、コンパイラーにシャットダウンするように指示します。間違えた場合、通常は実行時にしかわかりません。そして、実行時に見つけることの問題は、これが顧客の問題かもしれないということです。


5

Java、c#、およびc ++は厳密に型指定された言語ですが、厳密に型指定された言語は柔軟性がないと見なすこともできますが、コンパイル時に型チェックを行う利点があり、特定の操作で間違った型を使用することによって発生するランタイムエラーから保護されます。

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


5
C ++では、すべてのキャストが「バイパス」型安全であるとは限りません。
James McNellis、2010年

4
すべてのキャストがC#の「バイパス」型の安全性をキャストするわけではありません。

5
すべてのキャストがJavaの「バイパス」型の安全性を保証するわけではありません。
エリックロバートソン

11
すべてのキャストがCastingの「バイパス」タイプセーフであるとは限りません。ああ、そのタグは言語を参照していません...
sbi

2
C#およびJavaのほとんどすべての場合、システムはランタイム型チェックを実行するため(フリーではありません)、キャストによってパフォーマンスが低下します。C ++では、dynamic_cast通常static_cast、実行時の型チェックを実行する必要があるため、は通常より低速です(いくつかの注意点があります:基本型へのキャストは安価です)。
Travis Gockel

4

一部のタイプのキャスティングは非常に安全で効率的であるため、キャスティングとはまったく見なされないこともあります。

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

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

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

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

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

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

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


2

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

私の経験では、このような規則に従うことで、2つのことが保証されます。あなたはひどいプログラマーになったり、優れたプログラマーになったりすることはありません。

はるかに優れたアプローチは、これらの全面的な禁止の背後にある真実の核を掘り下げて明らかにし、機能を慎重に使用することです。これらの機能は、仕事に最適なツールたくさんあること理解しています。

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

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

確かに、キャストを使用するの適切でない状況はたくさんありますが、これらも学ぶことが重要です。上記の回答はそれらのうちのいくつかを指摘するのに十分に役立っているので、あまり詳細には触れません。


1

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

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



1

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


1

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

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

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

キャストすると、2つの理由が考えられます。

  1. あなたは型の関係を表現するのに悪い仕事をしました。
  2. 言語のタイプシステムは、単にそれらを表現するのに十分な表現力を備えていません。

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

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


0

通常、テンプレート(またはジェネリック)は、キャストよりもタイプセーフです。その点で、キャスティングの問題はタイプセーフであると私は言うでしょう。ただし、特にダウンキャスティングに関連するもう1つの微妙な問題があります。それは設計です。少なくとも私の見方では、ダウンキャストはコードのにおいであり、私の設計に問題がある可能性があることを示しているため、さらに調査する必要があります。なぜ単純なのか:抽象化を正しく「取得」すれば、それを必要としないだけです。ところで素敵な質問...

乾杯!


0

本当に簡潔に言うと、移植性が理由です。どちらも同じ言語に対応する異なるアーキテクチャでは、たとえば、異なるサイズのintが使用される場合があります。したがって、ArchAからより狭いintを持つArchBに移行した場合、せいぜい奇妙な動作が見られ、最悪の場合はセグメンテーション違反が発生する可能性があります。

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

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