C++のここがダメ、あるいは、何故私はC++を捨ててC#を選んだのか

気が向いたので続きを書くぞ。

C++がダメというよりは、C++/Windows(要するにVisualC++)という開発環境がキツイと感じた、というほうが正確な言い方かもしれない。だって、C++という言語自体は結構好きだったから。あのマニュアルでコンピュータを操作している感覚とか、「あ、俺ちょっと今あぶない橋渡ってるなぁ」という感覚とか、C#Javaではなかなか味わえない。でもね、その代わりにというか、うへぇと思うことも多々ありまして・・・。
※ 書き始めて気付きましたが、C++から離れすぎて細かい事情をすっかり忘れてますね。間違ってたらどなたかツッコミお願いします。

MFCがダメ

C++のブームはMFCのブームによってもたらされたと聞くけれど、C++の衰退もやはりMFCによってもたらされるのかもしれない。もちろん、Windows限定の話だけれど。
ホントかどうかは知らないけれど、MFCはワザとオブジェクト指向っぽくない設計になっていると聞いたことがある。既存のC/Win32SDKプログラマに分かりやすくするために、敢えてWin32APIが剥き出しになったクラスライブラリにしたという話だ。それが功を奏して、見事にBorlandのクラスライブラリに打ち克ったらしい。
この話を聞いたときは、Microsoftって会社は実にずる賢いなぁなどと思ったものだが、Joel on Software を読んでから考えが変わった。むしろ、Microsoftは既存の顧客(この場合はC/Win32SDKプログラマ)を徹底的に大事にする姿勢を貫いている、と考えるべきなのだろう。
だけどね、MFCはちょっとツライ。ツライものはツライ。

リソースIDとか

メニューとかボタンなどのリソースIDのトラブルには辟易した覚えがある。複数のDLLでリソースIDが衝突を起こすのだ。そのせいでメニューをクリックしても別のDLLに定義してあるコマンドが起動してしまったり。
あー、なんだっけ、もう忘れたなぁ。何かこう、どのDLLのリソースをONにするかみたいなAPIがあって、それをあちこちに挿入して解決した記憶がある。そんなバッドノウハウはもうたくさん。

コンパイラオプション

えっと、あれれ、これもよく覚えてないぞ。ちょっと離れるとすぐ忘れちゃうもんだなぁ。確か「MFCを共有DLLで使う」「MFCをスタティックライブラリとして使う」とかそんなオプションがあったと思う。この手のオプションが複数のDLL間で異なっていると、リンカがエラーを吐いたりするんだよね。fooというシンボルは既にbarで定義されています、とかなんとか。あー、うざいうざい。

newとdeleteは同じDLLで、という制約

A.DLLの関数でnewしたオブジェクトは、A.DLLの関数でdeleteしないとトラブルが起こる、という罠。例えばリリース版のDLLでnewしたオブジェクトを、デバッグ版の環境でdeleteすると落ちるんだったか、メモリリークするんだったか。
そんなわけで、delete用の関数を用意するのか、operator deleteを定義するのか、boost::shared_ptr使ったときはどうなるのとか、面倒だった。あと、うろ覚えだけれど、std::map とかをDLLをまたいで引数渡しすると同様の理由で落ちた気がする。

マクロの罠

マクロについてはこんな経験がある。まず、次のようなクラスを作った。

class Hoge {
public:
  void MessageBox();
};

ところが、MessageBox 関数を呼び出すとリンカがエラーを吐きやがる。よくよくリンクエラーを読んで、はたと思い当たった。windows.h には次のようなdefineがされていることに。

#ifdef UNICODE
#define MessageBox MessageBoxW
#else
#define MessageBox MessageBoxA
#endif

はい、何が起こったかはもうお分かりですね。もう勘弁してくれよ、って感じ。

ビルドが遅い!

フルビルドすると1時間かかるとか、もうイヤ。
ビルドが遅いと、リファクタリングがイヤになるわけで、だからスパゲッティコードがどんどん増える。#includeの根元のほうにあるヘッダファイルとか、絶対触りたくないもの。
C++に比べたら、JavaとかC#ってコンパイル速いよねー。奥のほうのクラスも平気でがんがんリファクタリング出来る。これって結構品質に影響することだと思う。

DLLの中にクラスはいない

C++でクラスを定義しても、DLLに定義されるのはマングルされた関数だけ。クラス、という実体がDLLに埋め込まれるわけじゃない。そんなわけで、hoge.dllとhoge.libとhoge.hを3つまとめて配布しなくちゃいけない。
そう、それだけ、それだけのことなんだけど、たまにミスるんだよね。hoge.dllとhoge.hが食い違ったりとか。この手のトラブルって、気付くまでにかなり時間を浪費してしまうことがあって、原因に気付いたときには非常にげんなりする。

デバッグ作業が見積もれない

デバッグ作業の見積もりは難しい。C++はなおさらだ。
Debug版では動くんだけど、Release版では動かない。なんで?
あれ?どうしてここでこの変数の値が書き換わっちゃうの?
デバッガがメモリリークを検出するんだけど、どこでリークしたのか全然分からない。・・・
こういった問題、つまり初期化忘れとか、バッファオーバーランとか、デバッグにどれくらいの時間がかかるのか事前には全く見積もれない。いつまでコードを追ってもさっぱり分からないときもある。
しかもね、この手の問題って、何で苦労しているのか説明しづらいのだ。お客さんにはさっぱり伝わらないし、上司だって分からない。だからトラブルの元にもなる。


・・・こんなところかな。
最後に、未だに残るC++に対する未練を書いておこう。
まずテンプレート。Modern C++ Designとか読んじゃうと、テンプレートはたまらないよね。
あとそれに関連するけど、STLは素晴らしかった。あれに比べると.NETのCollectionsはダサいなぁ、と思う。