キャストは使うな!

近頃、色んな場所で、C++のコーディングについて、
キャストはできるだけ使うな!
と書いてありました。
とすると、警告が出てしまうと思うのですが、
警告を出したままの方が良い、という事なのでしょうか?
それとも、void*などを基本使うな、という意味なのでしょうか?
段々と、自分のコードが近頃、標準関数に近しくなってきているのを感じていて、
constなども多用するようになって、見やすくはなってきてるのかなぁと思っています。
ただ、どれが正しい!というコーディングは無いにせよ、より可読性と精度は高めたいと思っています。
どうすべきなんでしょうかね…?
何か良い意見は無いでしょうか?

はい。いくつかの理由と、いくつかの実装的制限はありますが、「キャストは使うな!」は正しいです。
ただし、mallocを使わず、テンプレートをコレクションで使っている限り、という前提付きですが。あと、C++言語限定。C言語単体ではどうやってもキャストの罠から逃れられません。


まず、実装としての制限から。
C言語は、型に緩い言語です。この緩さは、指摘の通りすべての参照を同一の型「void*」に還元できるところから来ています。そして、C言語の標準ライブラリはこの緩さを使った実装になっています。
たとえば、メモリ確保関数のmalloc()はvoid*を返します。そして、このポインタは、プログラマーが自分で管理する限り、どのように使っても構いません。
もう、この時点で問題があります。言語としては「特定の型」でなければ正常に動作しないように作れるにもかかわらず、実際にはどんな型であっても構わないのですから。
たとえば、

int *hoge=malloc(sizeof(char));

なんて書き方ができちゃいます。これ、明らかにおかしいですよね。じゃあ、こう書いても良いのかというと、

int *hoge=(int*)malloc(sizeof(char));

確かにワーニングは出なくなりますが、なんの解決にもなっていません。つまり、ワーニングを消すという行為自体がバグの温床になっているのです。


で、どうするかというと、

  • 可能な限りnewを使う
  • qsort,fget,memcpyなどのvoid*を引数に持つ関数を使わない
  • コレクションはテンプレート経由で使う

とすると、型チェックが厳密になり、キャストを使わなくてもワーニングが出なくなります。ワーニング自体がバグの温床であることは明確なので、きちんと書いてバグを出さないというのが本筋ですよね。
また、型は使用想定を意識したものですので、型があっていると言うことは、使用想定があっていると言うことに他なりません。つまり、型自体が抽象化の対象となっているといえます。


でも、実はこれでもまだ安全じゃありません。

void hoge(int* ptr)
{
   for(int i=0;i<10;i++)
   {
      ptr[i]=i;
   }
}
void main()
{
   int fga[5];
   hoge(fga);
}

が、ワーニング無く通っちゃうのです。
確かに静的型チェックは便利ですが、バッファーオーバーランには無力というか。
かといって、これはC言語が高級アセンブラである限り回避のしようがないんですよねぇ。
素直に書いてワーニングのでないプログラムを目指すことはもちろん重要ですが、それ以上に使用想定を無視してないかをきちんと意識したプログラムを書かないと危ないというのが、C言語の怖いところです。それぐらい言語側が担保しろよと言いたくなる高級ゲンガーですが、まだ組み込みではC/C++が主流ですからねぇ。


結論としては

  • キャストは使うな!
  • キャストしなくてもワーニングが出ないプログラムを
  • でも、関数呼び出しの時には細心の注意を

というところでしょうか。
ちなみに、constつき引数は「使用想定」をプログラマーに見せているという意味で非常に重要です。ぜひその線でいきましょう。