プログラミング所感 - 前へ 目次 次へ00/2/9(水)バグではないが VCにまつわる話を、といいつつ。いつのまにやら、2000年も過ぎた。2ヶ月以上ぶりの更新である。 家でも、1/1 を前に、食料を多めに買ったり、水をペットボトルに溜めたりした。 が、殆ど何も起きなかったね。只、原子力発電所関係で 2000年問題が発生したのには、はっきりいってあきれたね。 いくら関係ない所とはいえ、あれだけ、はっきり 「絶対に大丈夫です」と言い切った手前どうすんだ? (だいたい、いくら税金使ったんだ?) まあ、何も責任とらないと思うけど。でも責任とって欲しいね。 大丈夫だって言ったんだから。認識甘いというか、往生際が悪いというか。 話がのっけからずれてしまったけど。 今回は、VC で DLL を作っていたときのこと。始めは問題なかったのだが、 突然、「DLLMainが、Mfcs42.lib と MSVCRT.lib の両方で定義されている」と 出てリンクできなくなった。さっぱり分からなかったのだが、 ふと、stdafx.h を使ってないソースにも全部 stdafx.h を書いたら 直った。理由はなんとなく想像できるが、はっきり説明できない。 動きが挙動不審なんだよなあ。 直ったからまあいいか。
ちょっと錯乱気味に、また、話は変わるけど。ニフティのログロックの
コンテストを
しているサイトがあって、現在、14問出題されている。 P.S.後から気づいたのだが、件のページには「アルゴリズムで解くな」と警告があった。 始めはなかったと思うのだけど。私のせいでできた警告かもしれない。 まあ、あれだけ瞬時に最短手を解答していたら気づくでしょうね。 00/2/16(水)真円と楕円について。 かつて、真円(Circle)と楕円(Ellipse)を巡って、 データ設計上の意見の対立があった。1つは真円から楕円を派生させる方法(真楕式)であり、 もう1つは楕円から真円を派生させる方法(楕真式)である。
真楕式の言い分は「真円のメンバ(中心、半径)に対し、
楕円のメンバ(中心、長径、短径)は増えている」というものである。
しかし、これは間違いである。
第一に、半径は長径でも短径でもない。
つまりメンバは増えたのではなく、増減しているのである。
この関数は、真円でもそのまま使用できるため、派生クラスで 再定義する必要がない。それでは、楕真式のインターフェースを見てみよう。
さてさて、真楕式の賛成派は、上で定義した Circle のオブジェクト サイズが無駄に大きくなると、異を唱えるだろう。それを避けるためには、 次のようにすればよい。
しかし、この設計でも次の批判は残ってしまう。 しなわち「Circle が Fat Interface である」というものである。 確かに、Circle のインターフェースには無駄がある。 この無駄を取り去るには派生関係をなくすよりしょうがない。 しかし、楕円が利用できるものは(理論的に)真円でも利用できるので、 全くの無関係にすべきものでもない。 結局、次のようにするのがベストであろう。
こうすれば、Circle のオブジェクトでもキャストすることにより
Ellipse のメソッドが使用できる。また、この方法は、仮想関数を
使用しないので効率が良いし、メモリの節約になるかもしれない。
この方法は、生成式と呼ぶことにしよう。
例えば、実数と複素数の関係もこれにあたる。complex<T> sin(const complex<T>&) があれば、double sin(double) の代用になるが、double sin(double) を用意した方が便利なのは 明らかである。 00/2/28(月)ダイアログバーのボタンについて。 ヘルプには「ダイアログバーはそのまま使え」とあるが、以下の話では派生して使っている。 「ダイアログバーにボタンをつける」ということは普通はしないと思うので、あまり役立たない情報かもしれない。今回は、必要があって作ったのだが 3つ問題が生じた。 第1の問題は、ビットマップボタンにしたかったのだが、OnInitDialog がフレームワークから呼ばれない。 調べるとダイアログバーの場合、メッセージマップで ON_MESSAGE(WM_INITDIALOG, HandleInitDialog)として、 LRESULT HandleInitDialog(WPARAM,LPARAM);を代わりに使うようだ。 第2の問題は、せっかくHandleInitDialogを作ったのに、ClassWizard でボタンに割当てた変数に対し SetBitmap が失敗してしまう。どうも DDX がちゃんと動いていないようだ。 まあ、派生して使うなということだから仕方がない。これは、直接 ((CButton*)GetDlgItem(IDC_BUTTON1))->SetBitmap(bmp);として回避できた。
第3の問題は、最も深刻だった。ボタンが押せないのである。
他の人が作っているところで、できているようなので調べたら次のようにしてできた。
しかし、本人も何故そうしたのか覚えていないとのことだ。解決方法は、
「そのダイアログバーを持っているフレームクラスで、ボタンのハンドラを定義する」である。
このハンドラはダミーなので中は空でよい。 00/2/29(火)ビットマップの背景透明化について。 gif だと、背景色という設定があって、WebPage でよく使ったりするが、 MFC で ビットマップの背景透明化の方法を後輩が開発したので紹介する。サンプルの作成
00/3/10(金)3/8朝、営団で始めての死傷事故が起きた。脱線した車両が対向車両と衝突したのだ。 東京新聞記事
二日後の今日、営団は護輪軌条の基準見直しを検討している。
(読売の記事では
補助レールと記述しているが、護輪軌条という方がかっこいい。)
この雑記はプログラミング所感であるから、プログラミングの世界での
護輪軌条は何かを考えてみよう。
他には、ライブラリやフレームワークやデザインパターン、
あるいは整った開発環境の利用もあげられるかもしれない。
ソフトウェア設計では、バグの検出に重点を置いている気もするが、
バグそのものを出さない護輪軌条はもっとないのだろうか。 8/18 の話参照。 00/3/13(月)ファイルフォーマットのバージョン管理について。GUI アプリは、だいたい、VC++ で AppWizard で作るので、 ファイル管理は、Serialize 関数を書くぐらいで済んでしまう。 これまではそんなにヘビーに使われるものを作らなかったので、 ファイルのバージョン管理などしていなかった。 あるプロジェクトでは、必須の機能になったので入れたのだが、 要は、ar << 100; とか最初にバージョン番号を入れるだけである。 めちゃくちゃ単純だが、これがあれば後でどうとでも対応できる。 これがないと、後でバージョンアップするときにコンバータなりが 必要になってくる。 だから、これからはいつもバージョン番号を 最初に書くようにした方がいいかもしれない。 手間もサイズも殆ど変わらないし。 ちなみに、CObject から派生していれば、バージョン管理ができる(はず)。 しかし、ここでいっているのは、ファイルフォーマットのバージョンの話である。 CObject のバージョン管理は個々のオブジェクトの話であって、このオブジェクトが 増減したり、順番が変わったりしても対応できるはずがない。と思っているのだが あっているかな。CDocument は、CObject から派生しているからできるのかな? 5/22 の話参照。 00/3/14(火)最近、Java を勉強するようになった。動機はやはり Applet を作りたいからである。まずは、Webサイトで 勉強しようと思ったのだが、どうもよくわからないことが多くある。 それで、本をいろいろ探して買ったのだが、読んでみると JDK 1.0 しか 出てこない。さすがにしょうがないので、本を取替えてもらった。 今度は最新のものをと思って、Java2 の本にした。発行日は先月だから、 かなり新しい。名前は「コアJava2 Vol1」である。値段は、6800円と高いが、 非常によい。付録もよい。TextPadが便利である。80MB 以上もあるけど リファレンスがついているのもうれしい。 まだ、最初の方しか読んでないが、これまでの疑問が氷解していく。 確かに、Java は C++ に比べればやさしいのがわかってきた。 さて、ここからが本題。
また「よい言語が大衆に受入れられるとは限らない」ということもある。 OS もそうだけど。
ちなみに、前に書いた、内部クラスに関する疑問は、
上記の本を読んで理解した。結局、「内部クラスは、外側のクラスの参照を隠れて
持っていて使える」ということだった。
00/3/27(月)書くのも恥ずかしいのだが、大失敗をした。UNIX の 「rm -r」と同じことをする関数が必要だったので、 Windows API を調べたが、なさそうだったので自作した。 適当に作って実行したのだが、終わらないのでデバッガで調べてみると、 なんと!ディレクトリを上へ辿っているではないか。E ドライブで 実行していたのであるが、E ドライブ直下のファイルが、1つを残して 全部消えてしまっていた。顔面蒼白である。結局、何が消えたのかは 思い出せないのでわからない。あやふやな記憶では、重要なのはなかったと 思う(思いたい)のだが。。。 00/3/28(火)STL のコンテナの選択について一言。例えば、次のような場合
最初にサイズがわからない場合、list を使いたくなることがあるかもしれない。 しかし、途中に挿入や削除をしないのであれば、vector を使うべきである。 vector の方がメモリの点でも速度の点でも効率的なのは説明するまでもないであろう。 さらに、vector の反復子は ランダムアクセス反復子であるから恩恵もうけられる。 (例えば、stable_sort ができる。list を stable_sort したければ、一旦 index を vector で作り直すなどしないといけない。) vector で push_back を使う場合でも、最初にサイズがわかっているならば、 reserve を使うべきなのは言うまでもない。 次に、何でもかんでも map を使いたがる人が多いように感じる。 例えば、名前と社員コードがある「社員(Employee)」を作ったとすると
全体のデータを map で管理したりしていることがある。 これは、次のように直すべきだ。
dic は社員コードから index を変換するために用いる。 後は、index を使って data をアクセスするようにするのだ。 もちろん、アクセスのたびに dic を使っていたら元も子もないので、 極力 index のみ使うようにしなければいけない。 map は便利だが、まず vector が使えるか考えること。 但し、set の代わりにいつでも vector を使うのはやめた方がいいだろう。 おそらくは、よく考えれば set か vector のどちらを選ぶべきかは わかるはずだと思う。 00/3/31(金)インターフェースについて。Java で標準出力に出力するとき、
と書く。実は、こういう書き方が、私は好きである。 最初に、こういう書き方に出会ったのは、14年程前、CLU という言語である。 はっきり言ってマイナーな言語であり、私も殆ど覚えてないのだが、 Java と同じような書き方になっていた。もっとも、Java の out はオブジェクトだが、 CLU では(C++風に言えば) static な関数だったと思う。
基本的には、私はタイピングが少ない方が好きである。
しかし、C ならば、printf で済むところを、2,3 倍のタイピングを要するものを
何故、好むのか?
printf は、グローバルな空間にボンと置いてある。System.out.println で、
グローバルなのは System だけである。 これらは、つまるところ、名前空間(namespace そのものではなくもっと一般的な意味)の 導入にある。私の好きなのは、それが、標準ライブラリでも一貫して使用されている というところである。 00/4/5(水)わかりにくいコーディングについて。以前、「&& を if の代わり」に使って短く コーディングできるという話を書いた。 その後、fj.comp.lang.c で似たような話題が出て、ほぼ全ての人が 「わかりにくいのでやめた方がいい」というものであった。賛成派は 私一人ぐらいだったが、本当のところ、私もあまり好ましいとは思っていない。 基本的なスタンスは、「シンプル&イージー」といったところか。では、 「コンプレックス&イージー」と「シンプル&デフィカルト」ではどちらを選ぶか? これは、ケースバイケースであろう。fj.comp.lang.c で、言いたかったのは、この 「ケースバイケース」である。そこで、常に、否定的であるような意見に対し反論した のだが、意図を正しく伝えられなかったかもしれない。まあ、私の意見もまとまっては なかったのだが。
同じような話に、「カンマ演算子の使用」がある。カンマ演算子を使用すると
プログラムを短く書くことが可能である。しかし、たいていの場合、カンマ演算子を
使うとわかりにくい。
(C++ ではカンマ演算子をオーバライドできるのでさらにわけわからなくすることもできる。)
のような for の中である。ここでは、2つカンマ演算子を使っている。
1つめの k=0 を書いている理由は ++k を同じところで書いているので、
対比させるためである。2つめの ++k を書いている理由は、別のところに書くと
わかりにくくなることがあるからである。また、この場合は、スペースの入れ方にも
ちゃんと意味がある。カンマ演算子のところにスペースを入れることにより、
通常とは異なる記述方法を目立たせているのだ。
書いた本人は、func(a,b,0) のつもりだったのだが、 第 3 引数がなくてもよかった(デフォルト引数 or オーバロード)ため、 コンパイルでは何のエラーにもならなかったのだ。 ところで、次のような表記は欲しくなったりもする。関数が複数の返り値を持てて、代入もできるというもの。
但し、上の例は、コンパイルできる可能性があるが、もちろん意図した通りにはならない。 これ以上、C++ を複雑にしてどうするというのもあるけど。 00/4/7(金)反復子のシリアライズについての話は、 あまり聞かないが自分でちょっと考えてみた。 (本当にちょっと考えただけなので、たいした話ではない。)まず、次のクラスのオブジェクトをシリアライズしたいとする。
i は、l の中のどこかを指しているとする。(すなわち、Java のシリアライズみたいに 反復子の指している先を自動的に生成することは考えない。) どういう実現方法がいいのかよくわからないが、取りあえず、こんなのを書いてみた。
こんなのでも役に立つかな。 00/5/9(火)昔のプログラムのバグ取りをしてたら、また間違えていた。 四捨五入を i=(int)(x+.5); としていた。 (正しくは、i=(int)floor(x+.5); 負の場合にずれる。)「三つ子の魂百まで」か。基本的な所は最初にちゃんと覚えないとダメだね。 00/5/10(水)デフォルト引数の話。我ながらまずい設計だと思ったのが、これ。
最初、2引数の関数だけを用意したのだが、便利かと思って1引数のも追加した。 さらに、0引数も追加しようかと思ったのだが、1引数のデフォルト引数にした。 こうすると、
ちなみに、私はあまりデフォルト引数を使うのは好きでない。
プログラムが固定してしまったら、使うのはやぶさかでないが、
引数を変更する可能性があるときは、デフォルト引数があると
混乱しやすい。 00/5/11(木)ちょっと思いついて計測してみた。今回で62回目。書き始めてちょうど2年半だ。年度別では、
話は変わるが、記録しとけば良かったと思うのは、本である。 理由は、本屋で買いたいと思った本を既に読んだことがあるかどうか 分からないからだ。子供には「自分が読んだ本を記録する」習慣を教えるようにしよう。 私には、既に買ってある本のデータベースを作る気力はない。 というか挫折した。たぶん、単行本だけで2,3千冊あるだろう。
また話は変わるが、初心者に GUI アプリケーションの講習をする時に、
題材として何がいいだろう。VC++ チュートリアルでは、
お絵かきソフトを作らせている。あれは、いい題材だと思う。
簡単に高機能なものができたので感動した覚えがある。
00/5/15(月)再び、参照メンバの話。 参照メンバを使用していたらどうも思った通りに動かない。 もちろん、以前の話の原則
また、
では、参照メンバに関する鉄則を決めよう。
という具合。data[0],data[1],data[2] が height,width,id に相当する。
00/5/22(月)再び、ファイルのバージョン管理について。 前にも書いた話の続き。 フォーマットの最初にバージョン番号を入れておくと、 「アプリケーションがバージョンアップしても旧ファイルを読める」 ようにできる。そこで、もう一歩勧めて、「旧アプリケーションが新ファイルを 読んだら、その旨の警告を出す」ようにしておいた方がいいだろう。 できれば、更新方法の情報が出ればもっといいだろう。ネットにつながっていれば、 自動更新もいいかもしれない。ウィルスバスターではできているけど。00/5/23(火)VC++ のバグかと思ったのだけど、自分の勘違いだった。 メンバが3つあるやつで、それぞれを第1キー、第2キー、第3キーにして比較 するところを次のように書いていた。
この関数は間違っているが、どこがおかしいか判るだろうか? 答えは、次の通り。
何ともお粗末でした。 00/5/24(水)Undo について。初の3日連続更新だ。 (ううむ、現実逃避だ。)Undo の実装は大きく分けて、(1)差分を覚える(2)全部覚えるの 2種類あるだろう。10数年以上前であれば、ハード的な制約から(1)にせざるをえない こともあっただろうが、現在では気楽に、より実装しやすい(2)の方針を取れるだろう。 通常は、Data を必要なデータとしたとき、
として、リストと現在のデータを指す反復子で管理できるだろう。 あとは Undo したい操作の直前で現在のデータを複製(MakeClone)するようにする。 実際に Undo するときは、m_cur をデクリメントするだけでよい。 また、Redo も簡単に実装できるだろう。 さて、MakeClone の実装だが、始め次のようにしていた。(細かい所は省略)
もちろん、これでも問題なく作ることはできる。しかし、MakeClone するとデータのメモリアドレスが 変わってしまう。結果として、外部にデータを指す反復子を持っていると、更新しないといけない。 そして、更新忘れからバグが生じやすくなっていた。で、ちょっと考えれば次のようにすればよかった。
こうすれば、Undo がないときと同じように書いても問題ない。実に簡単。 00/5/26(金)参照に関する VC++ のバグ。 (惜しいな、昨日書けば5連続だったのに)
(2)はエラーになるのに、(1)はエラーにならない。 gcc ならばもちろん(1),(2) ともエラーになる。 よくある間違いで、オリジナルを設定しているつもりで、コピーされた一時変数を 設定していることがある。つまり、参照(&)の付け忘れだ。 ところが、ちゃんと参照を付けているのに、変更されないことがあった。 で、調べてみると、VC++ のバグであった。一時オブジェクトの const でない参照が 取れてしまうのだ。これは、是非ともすぐに直して欲しいなぁ。 00/6/13(火)再び参照メンバの話。 「STLによるコンポーネントデザイン」という本を読んでたら、参照メンバを使っている所があった。 もちろん、私の前に書いた鉄則に従っているわけではない。 実際の所、ポインタで書いてもいいもので、単に * つけるのが面倒なので 参照にしているような感じしかしない。まあ、参照だと初期化忘れがないというメリットはあるけど。 参照メンバを使っているのは、関数オブジェクトなのだが代入できないのは、やっぱまずいんじゃないのかな。00/6/22(木)set の話。set<Any> を使いたいとき、必要な要件は、Any がメンバ関数 bool operator<(const Any&)const を持つか、グローバルな関数 bool operator<(const Any&,const Any&) が定義されていることである。 このとき、set のメンバ関数 find も使える。 混乱しやすいのが、ヘッダ <algorithm> の std::find である。これを使うためには、 operator== が必要になる。従って、同じ find でも結果が異なるかもしれない。 基本的には、同じ結果になるように operator== を定義すべきである。 もし、そうなっていないと、
このようなときに、insert が失敗することがある。 (insert の返値はチェックしといた方がいいかもしれない。) set のメンバ関数 insert には3種類ある。ちょっと勘違いしやすいのが、 iterator insert(iterator it, const value_type& x);(1) である。 これは、pair<iterator, bool> insert(const value_type& x);(2) と ほぼ同じである。違いは、insert される x が it の直後にあるときに速くなることだけである。 従って、通常は (1) と find を対で使うべきであろう。 00/6/23(金)オブジェクト指向の話。オブジェクト指向の本質とは、「公開していいものはメンバ関数だけでメンバ変数は公開すべきでない。」 と思っている人が多い。それは、違うと思う。 本質は、「再利用しやすいインターフェースにする」ということである。 このとき、多くのメンバ変数は非公開にした方が再利用しやすいが、 公開したままでも構わないものも存在するのだ。 (例えば、std::pair とか。) また、ある局面では、メンバ変数をそのまま公開しても構わなかったが、 別の局面に変わった時に非公開にした方がよいということも出てくる。 それは、必要が生じた時に変えればいいのだ。再利用化とはそういうステップである。 「メンバ変数は全て非公開」より「Keep It Simple, Stupid」の方が重要である。 「メンバ変数は全て非公開」というお題目を唱える人たちは、 そうすべき理由の前提が間違っていることに気づいていない。 00/6/29(木)目次が微妙に変わったのに気づいただろうか? 実は、自動作成するようにしたのだ。30分ぐらいで作ったが、2000年問題が発生した。 (ファイル名を見れば気づくでしょう)結構楽だ。最初から、こうしとけばよかった。
話は変わって、VC++ の新顔のキーワードをいくつか。
次に、explicit だが、これは良く使う。基本的にユーザコンストラクタ
(デフォルトコンストラクタ、コピーコンストラクタ以外のコンストラクタ。たった今命名)
では付けるように考慮すべきだろう。 全然関係ないが、オープンソースで簡易 Visual SLAM みたいなものを作るかな。 00/7/6(木)C++ の演習を教えていたのだが、 訳のわからないエラーに遭遇した。
上のプログラムが、VC++6 で実行時エラーになってしまう。 原因は「浮動小数点ライブラリがリンクされていない」からであった。 例えば、sscanf の前に、d=0; とか入れておくと大丈夫となる。 00/7/12(水)fj.comp.lang.c++ もレベルが落ちたね。 まだ、2チャンネルの方がためになるような気がする。 fj でまともな議論ができるのは数人しかいないようだ。 まともな人とレベルの低い人が議論しても、 まったくの無駄のようである。(だから、みんな STL 使おうよ。 使いもせずに間違ったこと言わないようにしようよ。恥ずかしいから) 00/7/14(金)MS が C# というのを発表したそうだ。 詳しく知らないが、ガベージコレクション(GC)もついているそうだ。前にも書いたが、GC は要らない。
Java で「全てのクラス変数が ポインタ であって GC が装備されている」
ことは大失敗ではないかと思っている。
このようにすると、3つファイルができるが実態は1つである。すなわち、aaa を修正すれば、 bbb,ccc も修正される。しかし、3つのファイルは同列である。aaa を消しても bbb,ccc のファイルは残り、中味も残る。そして、bbb,ccc も消すと、実態も消える。 これは、Java の世界と同じである。
このようにしても、3つファイルができ実態は1つである。しかし、実態は aaa そのものである。
従って、aaa を消してしまうと、bbb,ccc のリンク自体は残るが参照先がないので、
bbb,ccc の中味を見ることはできなくなる。
メモリ管理に悩まされたことがあるプログラマは、GC が必要と感じるだろう。
私も、メモリ管理でわけわからなくなってプログラムを捨てざるを得なくなったことがある。
しかし、繰返すが GC は必要ない。必要ならば自分で作ればいい。
それに STL を使うと、new や delete を書かずにプログラムを書くことができる。
(なおかつ、多様性も使うこともできる)大事なのは、「所有」か「参照」かを
はっきり区別することである。
コンテナに何を入れるかが
非常に参考になるだろう。(今日はまだできていないけど)
00/7/19(水)ドラッグ&ドロップについて。Windows のヘルプを読むとドラッグ&ドロップに対応するために、 COleDataSource や COleDataObject を使わないといけないようで、 面倒なのかと思っていた。で、ある所の Tips に、エクスプローラから のドラッグ&ドロップの対応方法に書いてあったが、割と簡単であった。
00/7/21(金)ダイアログコントロールの変数の配列化について。ダイアログ画面を作成していると、よく、何列も同じコントロールを並べたりする。 例えば、IDC_EDIT1, IDC_EDIT2, ... というような感じでエディットボックスを作ったりする。 これらを、ClassWizard で変数を追加すると、m_edit1, m_edit2, ... というようにしかできない。
このままだと不便なので、CString* m_pEdit[N]; とメンバを追加して、
ダイアログのコンストラクタで、m_pEdit[0] = &m_edit1; ... などとして
配列で使えるようにするのだが、結構、面倒である。 00/7/24(月)WinZip について。プロジェクトの最新ソースを圧縮してサーバに置くようにしている。 WinZip だと、Explorer からポップアップメニューでフォルダごと圧縮できるので、 便利である。 圧縮ファイルは、履歴を残すためにファイル名を変更していた。 例えば、圧縮したファイルが XXX.zip の場合、XXX0724.zip のようにその日の日付を追加していた。 この作業を自動化したかったのだが、どうしたらいいか困ってしまった。 圧縮は、メニュー一発でできているので、コマンドラインからもできるはずである。 しかし、WinZip のオプションがわからない。 試行錯誤を繰返すこと以下の通り。
後、ファイル名に日付を追加する部分は、C++ で書いて、元々のやりたいことを実現できた。 00/7/27(木)modeNoTruncate の使い方を間違えた。たいした話じゃないのだが、MFC の CFile で Open するとき、 modeWrite でオープンしているのに、modeNoTruncate を付けていたため、 ファイルにゴミが残ってしまった。このバグは、上書きで、前より少ない量を 書かないと気づかない。modeNoTruncate は、 Open(filename, CFile::modeCreate | CFile::modeNoTruncate | CFile::modeRead); という形でしかまず使わないであろう。であれば、最初からこの3つを1つにして欲しかった。 00/7/28(金)プロファイラについて。あるアプリが遅いので、高速化しようと思った。 最初、O(N^2) の部分を O(N) になるように直してみた。 しかし、全くスピードが変わらない。そこで、プロファイラを使ってみると、 修正した所は、元々あまり時間がかかっていなかったので元に戻した。 次に、プロファイラで一番時間がかかっている所に着目した。 ファイルの読込み部分に時間がかかっていたので、一旦、ファイルの内容を メモリに持つように修正した。この結果、時間が約70%減となった。 次に時間がかかっている所を、何も考えず、inline に変えてみた。 結果は、逆に、わずかに遅くなった。よくよく見るとその関数は 呼ばれる回数が少ないので、意味がなかった。inline の鉄則を忘れていた。
さらに、プロファイルをとってみると、vector の insert で時間がかかっていたが、
insert は使っていない。これは、push_back の中で呼ばれているものだった。
私は、vector でサイズが分からない時、push_back をよく使っている。
今回は、サイズが予め分かっていたのだが、reserve しておいてから、
push_back していた。この部分を、reserve を resize に直し、push_back を
使うのを止めたところ、さらに時間を約25%減らすことができた。
push_back に時間がかかるのは意外であった。g++ だとまた違うかもしれない。 ここまで来て、最初に修正した「O(N^2) から O(N)」の部分をもう一度修正してみた。 すると今度は、時間が約7%減となった。 一般に、高速化を行う場合プロファイリングした結果を使って行うのが妥当とされている。 しかしここで感じたのは、「例え重要でない所でも、オーダが変わるような変更は 行った方が後々よいことがあるかもしれない」ということである。 また、vector で operator[] を使用している所を反復子に変えてみたりもしたが、 殆ど変わらなかった。他にもいくつか変更を試みたが、時間の変わりがない所は 表現の簡単な方を採用した。今回、久しぶりにプロファイラを使ってみたが、 なかなか興味深かった。新人にも研修でやらせてみるかな。 00/07/30(日)inline 関数について。「Efficient C++」というプログラミングの本を買った。 最初、「Effective C++」のパクリかと思ってた。 ところが、かなり評価できる内容である。 細かい間違いはあるが、概ね正確で網羅的である。 内容の大半は、これまでも注意している事柄であったが、 新たに気づかされることもあった。例えば、 パフォーマンスのネックが、new や delete にあるとき、 これまではあきらめていたが、独自の定義で充分改善できることがわかった。 (もっとも、改善の主な原因は inline によっている。) また、よく誤解されている「vector は 組込み配列より遅い」という ことについても充分な実験結果を通して、説得力のある反論を展開している。 (fj.comp.lang.c++ の参加者もみんな読んで欲しいものだ。)
この中で興味を引いたのが、inline の使い方である。
効果的な方法がいくつも紹介されている。
inline 関数を無効にしたい時は、何も定義しない。 有効にした時は、INLINE_IND=inline を定義する。 (VC++6 では、INLINE_IND を INLINE にするとおかしくなるので注意) もっとも、このテクニックは、単純な Make しか使えない環境では、 避けたほうが懸命であろう。しかし、VC++6 では、効果がある。 すなわち、非 inline 指定時に XXX.cpp を修正しても、コンパイルし直すのは XXX.cpp だけである。これは、VC++ が #if ディレクティブ を 理解ながらコンパイルすべきファイルを決定しているからである。 (プリコンパイル済みヘッダを使用していても問題なく使える。) しかし、inline 指定時は、コンパイルするファイルが増えてくるので、 inline 関数をヘッダに移した方がいいかもしれない。 00/07/31(月)シリアライズのための設計時のポイントについて。シリアライズのことを考えると、MFC は魅力的である。 (必ずしも効率的ではないが) しかし、CObject から派生させたくないこともあるだろう。 そのときは、以下のポイントを考慮したほうがいいだろう。
00/08/01(火)vector の at について。STL は一般的に効率がよい。これは、一般的な状況においてであり、 特殊な状況ではもっと効率のいい方法が存在するかもしれない。 この辺の話は、「Efficient C++」に詳しい。 STL の中でも vector は最も効率がよい。但し、メンバ関数 at は 使わない方がよい。STL から外した方がいいだろう。 理由は、非効率的だからである。添え字の範囲チェックは必要なものだが、 常に必要なものではない。同じコードでスイッチできる方がいい。 ということで、私は、ライブラリのヘッダ <vector> を以下のように修正している。
MFC を使用していれば、ASSERT は定義済みである。MFC を使用していなければ、 自前で定義すればいいだろう。(#define ASSERT assert または、ASSERT=assert) 00/08/08(火)マウス操作のハンドラについて。MFC なら、OnLButtonDown, OnMouseMove, OnLButtonUp を 作ってやればよい。問題は、操作の流れが分断されるため、プログラムに フラグがたくさん出てきて分かりにくくなってしまうことである。 処理のモードが増えると、保守が大変にもなる。 以下に、解決方法を示す。
CheckMove がドラッグ中は座標を設定して、true を返す。マウスを離したら false を返す。
状態に応じて、処理を切替えるには、Java の MouseListener 相当の
クラスを作ることもできる。もっと、簡単に enum か int のフラグで
処理することもできるだろう。どちらがいいだろうか?クラスを作った方が
柔軟ではある。結局は、どちらも一長一短があるので、便利な方を使えばいいだろう。
組合わせてもいいだろう。 00/08/09(水)set を使ってバグってしまった。ある機能で、項目数が足らないというバグが出た。調べてみると、 データを一旦 set に集めてから処理していた。この時、比較関数上で同一と 判断された項目が上書きで消えていたのだ。面倒なので、multiset に変更した だけでバグを修正した。本来、やりたいことはソートだけだったので、正しくは、 vector に入れて sort すべきだったと思う。 00/08/18(金)前に護輪軌条の話を書いたがそれに関して。会社でも「インスペクションを徹底しよう」ということになってきている。 品質を向上させるためには、作成後すぐに検査することが重要であるようだ。 (参考ソフトウェアエンジニアのためのホームページ) つまり、各工程後にインスペクションを実施するのが良いということである。 前の話は、「コーディングの工程におけるバグの混入を防ぐには」というつもり だった。(バグの混入は他の工程でも起こり得る) そういう意味では、コードレビューが効果的ということなのか。 実際、 レビューだけでテストしないという技法もある。 (この技法によれば、WindowsNTやWindows2000 は捨てた方がいいと言えるようだ)
私と言えば、かなり適当である。要求仕様書からいきなりコーディングしてしまうぐらいである。
設計といってもメモ書き程度のことが多い。 00/08/21(月)書き方について。double 変数を 2で割りたいとき、d /= 2.; とは書かないで、 d *= 0.5;と書くようにしている。割り算より掛け算の方が速いと思っているからだ。 しかし、アセンブラを見てみると、どっちのコードも同じアセンブラになっていた。 (共に掛け算になっている。) では、d /= 1.23456; はどうなるだろう?やっぱり掛け算になっている。 定数の逆数をコンパイラが計算してそれをつかっているみたいだ。なるほど。 では、定数でないときはどうだろう?今度は、ちゃんと割り算をしている。まぁしょうがないね。 整数演算でも、i *= 10; などは、4倍して足して2倍している。 最適化は、コンパイラに任せた方がいいということか。(コンパイラは例によってVC++6) 00/09/01(金)名前のつけ方について。プログラミング時間のかなりの割合を占めているのが、 名前を考えている時間である。 結局は適当につけてしまうのだが、悩むことが多い。 心がけていることは、英語を使うようにすることである。 (つまり、atai とかしないで value としたりするということである。) ローマ字だとプログラムが読みにくいし、幼稚に見える。 長い名前も好きでない。書くのが面倒だし、横に伸びると印刷したときに、 折り返されて読みにくいからだ。 とはいっても、長い名前にした方がよいときもある。 短すぎて意味がわからなくてはしょうがない。 クラスのメンバ関数では、クラスで性格付けされているので、短い名前でも 十分なことが多い。 例えば、CFile f; f.Open(...); などは、 「ファイルのオープン」だと見て取れる。しかし、長い名前にしておいた方がいいこともある。 よく困るのが、どこで読込みをしているのか調べようとして、 読込みの関数 ReadFile を検索すると、数十個も出て来たりするときである。 各クラスに ReadFile があるため、こんなことになったりする。このときは、名前を 変えておけば良かったと思う。(例えば、ReadFileDisplay とか) この点、CRect のメンバ関数は見習った方がいいかもしれない。殆どのメンバ関数に Rect という単語が含まれている。「CRect のメンバ関数なのだから Rect はなくても いいんじゃないか」と思う人もいるかもしれないが、あった方がいいこともあるのだ。 00/09/13(水)初期化の仕方について。
このように初期化の方法には、2通りの方法がある。 この場合は処理は全く同じになる。私は、(1) のように書くことが多いが、 (2) のように書く人も増えている。(2) は C では書けなかったため、私は (1) のように書く習慣がついてしまっているのだ。
組込み型でない場合でも、(3) と (4) は共に初期化処理となり同じである。 ((3)は代入ではない。) しかし、見た目以外に 2つの点で (3) と (4) は異なる。 1つは、引数を2つ以上に変更する場合である。
このように、(3) から (5) に変更するのは面倒である。
このときは、(6) のように書く方がいいと思う。 00/09/14(木)反復子について。STL(vector,list,map,set,multimap,multiset) の反復子(iterator, const_iterator)は、 どれも 0 を代入することができる。 (ちなみに、reverse_iterator や const_reverse_iterator はできない。) 規格で求められているわけではないが、私の使用しているコンパイラ (VC++6.0、gcc 2.95.2)ではどちらもそのように実装されている。 (注:反復子に求められているのは、インターフェースのみである。)
反復子に 0 を代入している時に呼ばれているのは、反復子のコンストラクタである。
(vector は除く)従って、代入だけでなく、0 と比較することも可能である。
直した所(該当行のみ)
00/09/18(月)初期化の話の続き。 前の話では、初期化は括弧を使おうということにしたが、 実際にやってみると、いろいろ不都合が出てきた。
上の様に書くと、VC++6 ではシンタックスエラーになる。「=」を使えば OK となる。
変数が、組込み型で、かつ引数が定数でないときに括弧を使うとエラーになるようだ。
g++ では、エラーにならないので、VC++ のバグだと思う。
00/09/19(火)印刷でバグった。 斜めの字を印刷するとき、斜めのフォントを作成するのだが、 何故か、プレビューと実際の印刷で角度の方向が違うのである。 しょうがないから、印刷先が、プレビューかプリンタか判断して 書いていたのだが、忘れてしまっていた。それで、後で、不可思議なコードを見つけ 修正したつもりで、バグを作ってしまっていた。(実は私ではないけど) 結局、通常と異なる処理を書いたら、ちゃんと、コメントに残しておかないとダメだね。00/09/20(水)いつコンパイルするかについて。 PSP(パーソナルソフトウェアプロセス)のレビューをしていて、 「コーディングとコンパイルが分かれていない方がよい」という意見があった。PSP では、コーディング工程とコンパイル工程ははっきり分かれている。 (さらに、これらの工程の間にコードレビューが入る) 現実には、コンパイルをいつするかと言うと、コーディング中に頻繁にしている。 最近では、いうまでもなくコンパイルは簡単にできる。事実、多い時は 1日に数十回することもある。 特に C++ に顕著なのが、「コンパイルしてみないとわからない」というのがある。 すなわち、C++ の言語仕様があまりにも複雑なので、書いたプログラムが 正しいかどうかが見ただけでは分からないというのがある。 STL を使い始めた時に、訳分からないエラーメッセージが出て どこを直していいやら途方にくれた人も多いと思う。 慣れてくれば、どこを直せばいいかは分かるようになる。 しかし、STL を使っているとどんなに注意深く書いたとしても 一発でコンパイルが通るプログラムを書くのが、至難の技になることもある。 そのような状況では、コンパイルはもはやコーディングの一部と言ってもよいであろう。 結局、レビューでは、コンパイル工程は、コーディング工程に含めることになった。 00/09/21(木)Outlook の話。 私は Outlook が嫌いである。理由は押し付けがましいからである。 当然、IE も嫌いである。ある日、自宅の PC のディスク容量が足らなくなったなったので、 Outlook なんかいらんだろうと思って Uninstall した。 次の日、妻が「メーラが使えなくなった」と言ってきた。 まさかとは思ったが、Outlook を使っていたらしい。 「あれ、今まで Netscape 使ってたのに」と思ったが、 確認せずに消してしまったのは、私が悪い。 しょうがない、再インストールすることにした。 しかし、インストールできない。C ドライブに空きがないと言われる。 今まで入っていたのだから、バカな話だ。(まぁMS のやることだ。) C ドライブは元々、200MB とかぐらいしかないので、ダメそうだ。 Netscape を消して、D ドライブを空けた。しかし、C ドライブに IE5 が残っているのは不気味だったので、それも Uninstall して、 IE5 と Outlook を Dドライブに入れ直した。やれやれ、何時間もかかった。 どうにか、使えるようになったが、起動してみて驚いた。 なんと、Outlook の過去のメールもアドレス帳も全部残っている。 これはこれで良かったのだが、恐るべし Outlook。 C++ とは関係なかったな。 00/09/22(金)再利用について
定義 更新管理ができないもの(例:標準ライブラリ、商用ライブラリ)は、 いわゆるライブラリでありここでは再利用とは言わない。
再利用情報は、手を加えずにそのまま利用できれば、最も利用しやすい。
フレームワークなどは、追加修正することが前提となる。
使用する領域は、部品か全体かに分かれる。基本的には部品の方が多い。全体を再利用することは
非常に困難である。全体の再利用には、VC++ の CustumWizard が便利である。
部品としてのコントロールの再利用にはコンポーネントギャラリが便利である。
最初から再利用可能に作成することは、非常に困難である。 可能なのは保守性を高くすること(修正が容易になるように作ること)だけである。 再利用するために、どのように設計しなければいけないかは、 再利用が必要になった段階でようやく分かるものである。 00/09/25(月)オリンピック新記録。 日本女子陸上初金メダル。すばらしい記録にもかかわらず、 ゴールした高橋選手には、余裕が感じられた。インタビューにも、 「楽しい42kmでした」。すごいとしかいいようがない。
さて、C++ の話でもするか。C しか知らない人に C++ の参照を説明するのに、
どのようにしたらいいだろうか? ところで、(たしか)Ruby では、変数のスコープを表すのに変数名の前に記号を つけることになっている。私も g_、s_、m_みたいなものを付けている。 これは、非常によい仕様だと思う。ついでに、参照も何か記号をつけると良かったかもしれない。 よくバグの原因になるのが、参照のつもりなのに & を付け忘れてコピーしてしまっていることがあるからだ。 00/09/26(火)ファイルフォーマットについて。 通常のGUIアプリだと MFC のシリアライズを使う。 それ以外にデータベース的にファイルを使う時は、 自分でファイルフォーマットを決めることになる。 その場合、戦略的にテキスト形式かバイナリ形式かを選ぶ必要がある。 以前、テキストとバイナリの混在形式を使ったこともあるが、 混在形式は、使い勝手の悪いバイナリ形式ともいえるので、バイナリに含めることにする。 基本的にはテキスト形式を選ぶことが多い。テキスト形式の方が、 データが分かりやすいし、デバッグ作業も楽である。 しかし、パフォーマンスが気になる時は、バイナリ形式にすることもある。 その時も最初はテキスト形式にしておいて、安定してきたらバイナリに移行したりすることが多い。 バイナリ形式が有効なのは、クラスをそのまま読み書きする場合(浅いコピー)などであろう。 このときは、アラインメントを気にする必要があるかもしれない。 また、ファイルの一部を読み込む時、ダイレクトにその部分に到達するために、 バイナリにすることもある。
テキストファイルに限った話をすると、コメントが書けるというメリットがある。
(バイナリで書いても問題はないがあまり使うことはないだろう。そうともいえないかな。)
これが、結構重要に感じられる。ファイルのデータを使用するにつれ、
変更することがあるだろう。通常では、単に変更するだけである。
構成管理をきちんとしていれば変更情報も別に記録していることもあるだろう。
しかし、記録するのに最も適しているのは、そのデータそのものである。 コメントは本来の動作に不必要なものであるが、データファイルやプログラムソースに限らず、 付けるようにしておくべきであろう。 00/09/27(水)再利用の話で、 「ライブラリを除く」といっておきながら、例にライブラリを載せているのは、 なんか変である。まぁ、いいか。あと、初期化の話で、「組込み型かユーザ定義型か」でわけていた。 他の方法として、コピーコンストラクタを呼ぶ場合は「=」を使い、 それ以外のコンストラクタでは括弧を使う、というのでもいいかもしれない。 ふと、思っただけでそんなに根拠はない。 00/09/28(木)言語と開発環境について。後輩が、Visual C++ を言語と思っていた。言語は C++ で、 VC++ は開発環境であると説明したのだが、 「じゃあ、Visual Basic は?」 と聞かれて「うっ」となってしまった。 確かに、VB は言語でもあるが、開発環境でもある。 最近、VC++ を言語と思っている人が多かったのはこういうわけか。 00/09/29(金)ファイル管理でバグってしまった。 あるデータ(複数フォルダ、複数ファイル)を別の場所に コピーしておく機能を作成したのだが、いくつかのファイルで うまく機能していない。 Windows 用であるから、コピーするときに「ファイル名は全部大文字にしてくれ」と いわれたので、CString の MakeUpper という関数を使っていた。 これがまずかった。全角アルファベットも変換していたのである。(「a」を「A」に)このせいで、ファイル名が正しく認識されなくなっていた。 「ううむ、toupper はそんなことしないぞ」と思ったが、そもそも toupper は全角には使えない。 とりあえず、自作することにしよう。 00/10/02(月)グラフクラスの更新メモ。 グラフクラスを作成している。 無向グラフをベースに有向グラフを扱おうとすると、 あまり上手くいかなかった。どうも、有向グラフをベースにした方がいいようだ。 次の様に直すか。
(殆どメモ書きだな。) 00/10/10(火)[EC++] Effective C++ を久しぶりに読んでみた。 ([EC++] は Effective C++ の話題であることを示す。)第1版は読んでいたのだが、第2版を読んでみると結構新しくなっているようだ。 以前、読んだ時は Effective C++ に書いてあることを全部納得したと思っていたのだが、 あらためて読んでみると、納得いかないことがいくつか出てきた。何日かにわけて 納得いかないところを書いていくようにしてみよう。 Effective C++ は多くのプログラマーの成果の結晶であるから、それに楯突くのは無謀とも いえる試みである。いくつかは、玉砕するかもしれない。 さて、最初の納得いかないところ
しかし、これは Effective C++ の原則に反している。Effective C++ の原則は、非常に
すっきりしており説得力もある。さて、では何がいけないのだろう。
理由を考えていると、ようやく分かってきた。そもそも、関数オブジェクトを派生している
ところがまずいようだ。基底クラスでは共通部分をくくり出していたのだが、
よくよく考えてみると、基底クラスは派生クラスで実装として使っているようだ。
つまり、is-a の関係でないようだ。ここを派生にしているから原則に合わなくなっているようだ。
逆に言えば、基底クラスをユーザに提供しない(公開したくない)場合は、
実装を提供することになるので、派生関係にすべきでないということだろうか。
結局、原則は正しいという結論になった。(私の負け) 実装としての私的派生についての私の考えは、またの機会に書こう。 00/10/11(水)[EC++] さて、第2弾は is-a 関係にしよう。
って、もちろん反対しない。当たり前のことである。 では、どこが変かというと、「中で使っている例題」が変だ。 「正方形は長方形である(数学的事実)から、長方形クラスから正方形クラスを派生させてみる。 すると、このようになっておかしいこともある。だから、両者の関係を表すモデルに 公開派生を使うのは間違いである。」と言っているが、ここが変である。
「長方形クラスから正方形クラスを派生」させても何の問題もない。
(但し、普通は派生関係にしない方がよい)
もう1つ、例題を出してみよう。
中学生は人間である。よって、中学生クラスを人間クラスから派生させてみよう。但し、
人間クラスは自由に年齢の設定ができるものとしよう。また、中学生クラスの
年齢は 13歳から 15歳までとしよう。(現実には正しくないが便宜上)
00/10/12(木)[EC++] デフォルト引数とオーバライドについて
つまり、
なんて書くよりは、
こう書いた方がいいんではということである。 ふと、思っただけでいつもこうしているわけではない。
もう1つ。
このときは、bind などが有効かもしれない。 00/10/13(金)[EC++] マクロの話
さて、いきなり項目1 からである。 お題は尤もである。私の強調したいのは、「#define もまだまだよく使うよ」ということである。 本の中でも「全く不要ではない」とあるから、そんなにかけ離れた話ではない。 今回は、私の良く使う例を1つあげてみる。 20ぐらいの項目毎にソートする機能を持つ GUI アプリケーションを考えてみよう。 ソートはメニューから呼び出すようにしよう。つまり、メニュー項目が 20 あるとしよう。 実際にソートする関数は、DoSort(int type) を使えるものとしよう。 私は、このハンドラを書くときいつも次のようにしている。
ハンドラのプロトタイプは決まっているから、DoSort を直接呼ぶわけにはいかない。 従って、20個の関数を書かなくてはいけないが、中味はほぼ同じである。 かといってテンプレートも使えない。こういうときは、マクロが便利である。 話は変わるが、EC++ の項目1 に以下のような記述をできないコンパイラは古いと書いてあるが、 本当なのだろうか?VC++6 はまだできないけど。
g++ ができるのは知っているが、 てっきり g++ の拡張機能だと思っていたのだが。 00/10/16(月)[EC++] iostream の話。
この問題は、個人で開発しているときは「好き嫌い」で済むが、 チームで開発しているときは統一しないといけない。 現在のところ、私は cstdio を使うことで統一したいと表明している。 いずれは iostream に移っていくだろうが、まだまだ先のような気もする。 00/10/17(火)[EC++] 仮想デストラクタの話。
00/10/18(水)[EC++] 公開メンバの話。
この話は、前にも書いた。 原則として項目 20 は正しい。しかし、私は C++ のプログラムを何万行も 書いている。クラスの設計も数百はしているだろう。 その経験から言って、「全てのメンバを非公開にする必要はない」と言える。 嘘だと思うのなら、標準ライブラリの中を探検してみるといい。 公開メンバがごろごろ見つかるだろう。 簡潔に言えば以下の通りである。
00/10/19(木)[EC++] const の話
通常は const を積極的に使うようにしている。 00/10/20(金)[EC++] ダウンキャストの話
Java で書いたことがある人なら同意してくれるだろう。 00/10/23(月)[EC++] 私的派生の話
よく考えてみて欲しい。あなたは、ある実装を利用したいために私的派生しようと しているのであろう。私的派生ならば、「実装の利用」と「限定メンバ関数へのアクセス」の 両方とも実現できる。しかし、両方とも同時にできなければいけないのだろうか? クラス階層を検討してみて欲しい。「実装の利用」と「限定メンバ関数へのアクセス」は 別々のクラスですべきなのだ。そもそも、私的派生してしまったら、次の階層からは 限定メンバ関数にアクセスできない。限定メンバ関数は何のために限定にされているのであろうか? 次に、仮想関数を上書きしたい場合も派生せざるをえない。しかし、これも同じである。 私的派生はすべきでないのである。公開派生してそれをメンバに持てばいいのである。 いずれにしろ私的派生は必要ない。(ちょっと過激に言えば、C++ の言語仕様から 取り除くべきだろう) 重要なことは、「実現できるからクラスを作るのではなく、 どういう役割を期待してクラスを作るかである。」 ちなみに、私は限定メンバの使用には反対である。限定メンバ関数なら問題ない。 理由は friend と同じく、クラスに穴を開けるからである。 (公開メンバは許容しているんだけどね) 00/10/24(火)Ruby を始めた。 Perl によく似ている。しかし、結構違うところもある。 強烈な機能が多い。既存のクラスを派生せずに そのままカスタマイズできてしまうところはすごい。 というか、やばくないかな。例えば、class String def size 1 end end print "test".sizeこれは、「1」と出力される。
プログラムを動的に作成したいときは、かなり便利かもしれない。
00/10/25(水)Ruby の作者のまつもとさんは、 言語オタクと公言している。 「C++プログラミング所感」を書いている私もそうかもしれない。 私が使える(使えた)言語は、おそらく 20 は超えるだろう。 使えない言語(COBOL, PL/I, Ada, SmallTalk, Object-C, Object Pascal, Python 等)も リファレンスがあればどうにかなると思う。1つの言語を知っていれば、他の言語も分かりやすいということもある。 それに、私はもともとプログラミング言語が合っていると感じる。 これまでに覚えた言語の中で最も難しいのは日本語であろう。 まぁ、それは置いておくとして、プログラミング言語に限れば、 以下の3つが挙げられる。
PostScript(以下 PS) は、一般にページ記述言語と言われるが、れっきとした プログラミング言語である。しかし、分かりにくさでいえば、PS は最強である。 アセンブラより判りにくい。理由は、他の言語が「動詞+目的語」であるのに対し、 PS が「目的語+動詞」であるからだ。(これは日本語もそうである。) これが、どれほど判りにくいかは、適当なアプリケーションが生成する PS を 一度解析してから判断して欲しい。暗号と変わりないことを納得してもらえると思う。 3つ目の C++ が難しい言語であることは、言語仕様書のページ数からも言える。 この続きは、また後にするか。 00/10/26(木)C++ は複雑すぎる。 Java くらいがちょうどいいのかもしれない。 しかし、まだ Java オンリーにはしたくない。 散々けなされている C++ だが、私は嫌いなわけではない。 大学にいたときは Lisp が最も好きだったが、 今では C++ の方がよい。
Lisp はトップダウン開発に向いている。
C++ はどちらかと言えば、ボトムアップ向きだろうか。
(とはいえ、デザインパターンやフレームワークの支援を
受ければ、C++ でもトップダウン開発しやすい。)
これらの向き不向きは、変数の型の制約がゆるいかきついかによってくる。
しかし、大規模プログラムには型制限のきつい方がいいと思う。
00/10/27(金)また、Ruby の話だけど、 「C++/Ruby プログラミング所感」にするかな。 (Excel も入れるか)でも、面倒だからやめよう。
今まで、Ruby で作ったのは、離散系シミュレータと
会議室予約システム のCGI。どちらも大したものではない。
ここまでの感想は、やはり「リファレンス欲しい」である。
新しい本が出たようだし、また買ってみるか。
00/10/30(月)再び、私的派生の話。「C++ プログラミング所感」の読者の方からメールを貰った。 私的派生を使った方がよいという例だ。 (以下のプログラムでは、メールから引用させてもらっている)
このような固定長の配列のクラスがあるとしよう。
(このクラスは例なので、ここまでは「クラスがありき」とする。)
このようにすれば、
このときの違いは、MyArray3 では、MyArray2 より、メンバ zero の分だけ サイズが大きくなってしまうことである。 ZeroCleared のサイズは 1 だけどパディングのせいで 4 とか 8 バイト無駄にするかもしれない。 確かにこのときは私的派生の方がよい。(一本取られた。) 他にいい方法はないだろうか。
初期化させるというのであれば、このようにして、2種類の
コンストラクタを用意することもできるだろう。
(自分で呼ばないといけないけど) 00/10/31(火)組込み型とユーザ型の区別について。昨日の話の続きである。 話の目的は、「組込み型の時に 0 クリアしたい」であった。 ユーザ型では何もしなくて、組込み型ならば処理を行うということが、 可能だろうか?型を特定するのであれば、オーバロードすればできる。 では、型を特定しないままでできるか? この解答も、件の読者の方から教えてもらった。(以下のプログラムは貰ったメールからの引用)
さてさて、私はこのプログラムを実際に VC++6.0 と g++2.95.2 で
動かして、POD_ZeroInitialize が組込み型だけ 0 クリアしていることは
確認できた。しかし、原理は分からなかった。
考えても分からないので、原理も教えてもらった。簡単に言えば、
テンプレート引数が組込み型の時に、カンマ演算子などの2項演算子は
エラーにならずに無視されるということであった。 結論:組込み型とユーザ型を区別することはできる 00/11/01(水)メディアについて。お客さんに納品するときのメディアとして、最近は CD-R が多い。 理由を挙げてみると、
小さなシステムであれば FD(フロッピーディスク)でよいが、
最近では 1.4Mb で収まらないことが多い。
(CD-R は一応 650Mb といっているが、620Mb ぐらいに見ていた方がいい。)
次のコストパフォーマンスもかなりいい。最近、10枚 580円で買ったのだが、
1Mb が 0.1 円以下である。
納品だけでなく最近ではバックアップにも用いている。
まさに CD-ROM は大氾濫している。私は、CD-ROM がこんなにメジャーに
なるとは思わなかった。MO の方がもっとメジャーになると予想していた。
(書き込みできるから) 00/11/02(木)標準化について。納品物を作るという作業は、プロジェクトにつきものである。 UNIX 時代は 1/4 inch カートリッジをメディアにしていた。 そこで作成に必要な手順は、
これは、1つの例だが、作業の標準化をすることにより、効率よく 安心して工程を進めることができる。 但し、この業界の変化は激しいので、プロセスを固定してしまわずに 常に変化に対応できるように1人1人が心がけておくべきだろう。 00/11/06(月)コンセプトについて。この時期、新しいプロジェクトがいくつか始まっている。 私の担当するプロジェクトもイグニッションキーをまわし始めたところである。 プロジェクトのスタートアップにあたって、コンセプトメイキングしたくなった。 (具体的にはブレインストーミング) このアイデアは、eXtreme Programmingと 「iモード事件」という本から来ている。 よく言われることだが、プロジェクトの成功には明確な目標を持てというのがある。 そのためには、コンセプトを作り出すとよいように思う。 XP については、なるほどと思うところもあるので、またとりあげたい。 00/11/07(火)Ruby で作ったシミュレータについて。M/M/1 の離散系シミュレーションを書いたのだが、 1万単位時間ぐらいだと平気なのに10万単位時間実行すると、落ちたりしていた。 (M/M/1 は、待ち行列におけるケンドールの記号。 「到着が指数分布、サービス時間が指数分布、サーバが1台」を表す) ちなみにエラーメッセージは次のようになっていた。 ./sim.rb:17:in `<=>': undefined method `<=>' for #<Proc:0x457fb40> (NameError) from ./sim.rb:17:in `<=>' from ./sim.rb:17:in `sort!' from ./sim.rb:17:in `sched' from mm1.rb:44:in `Create' from mm1.rb:44:in `Create' from mm1.rb:44:in `call' from ./sim.rb:23:in `start' from mm1.rb:56最初は、エラーメッセージの意味を深く考えずに Ruby では、大規模なものは動かないのかと思っていた。 しかし、よくよく見ると sort! で失敗している。実行部分は次の通り。 @stack.sort!sort! の対象 @stack は、 [[time1,proc1],[time2,proc2], ...] というように、時刻とプロシージャのペアの配列である。 ここでようやく「time の比較で一致したとき、proc の比較演算子がないから失敗した」 ということに気づいた。(エラーメッセージを見ればその通りなのだが) そこで、sort! を次のように修正して、きちんと動くようになった。 @stack.sort!{|i,j| i[0] <=> j[0]}実数の実装は double だと思うけど、double の値がたまたま一致したということなんだろう。 スケジュールは指数乱数を用いているから、たまたま起こるというのは非常に珍しいような 気がするのだが、どうなのだろう。 結論:Ruby でも大量の計算で落ちることはない。 反省:エラーメッセージはよく読もう。 00/11/08(水)さすがに毎日更新していると、ネタがないな。 (その前に仕事してるのか?って聞かれそうだが)Shift-JIS の第1バイト目の判定は通常、次のように行う。 bool f1(int c) { return c >= 0x81 && c <= 0x9F || c >= 0xE0 && c <= 0xFC; }しかし、意味的には同じだが次のように書いた方が速くなる。 bool f2(int c) { return c >= 0x81 && (c <= 0x9F || (c >= 0xE0 && c <= 0xFC)); }実際に適当なサンプルで計測してみると約 30% ほど短縮された。 では、アセンブラでどうなるか見てみよう。 case f1 case f2 00401000 mov eax, | 00401000 mov eax, dword ptr [esp+4] | dword ptr [esp+4] 00401004 cmp eax,81h | 00401004 cmp eax,81h 00401009 jl 00401012 | 00401009 jl 00401026 0040100B cmp eax,9Fh | 0040100B cmp eax,9Fh 00401010 jle 00401020 | 00401010 jle 00401020 00401012 cmp eax,0E0h | 00401012 cmp eax,0E0h 00401017 jl 00401026 | 00401017 jl 00401026 00401019 cmp eax,0FCh | 00401019 cmp eax,0FCh 0040101E jg 00401026 | 0040101E jg 00401026 00401020 mov eax,1 | 00401020 mov eax,1 00401025 ret | 00401025 ret 00401026 xor eax,eax | 00401026 xor eax,eax 00401028 ret | 00401028 ret違いは1箇所だけである。「c >= 0x81」の判定に失敗した時の ジャンプ先だけである。実に単純だがいろいろ応用できるかもしれない。 00/11/09(木)ActiveX ネタ。 VC++ で「ActiveX コントロール」をサポートすると挿入される項目。
00/11/10(金)OR 学会誌の11月号(vol.45 no.11)の特集は、 「ヒューマンエラーからのリカバリ」である。 結構、面白い。 以下は、590ページからの引用である。
えらくまわりくどいが、私はこの内容に賛成である。
では、年頭の原発の2000年問題への怒りの原因は何であろうか。
それは、「口にしたことへの責任感のなさ」である。
00/11/13(月)始めてから3年過ぎた。 そろそろペースを落とすかな。私の小島研の修論の話をしよう。 (昔話はじじくさいが、自覚している) タイトルは「大規模線形計画問題を解く内点法のプログラム」である。 ちょうど 10年ぐらい前に Latex で 57ページ書いている。 懐かしくなったので、第0章 序章の§0.1 はじめにを引用してみよう。
この Karmarkar 法はその後、アルゴリズム特許で世間に知られるようになった。 ついでに次節も挙げよう。
60Kbyte って何行だろう。覚えてない。1000行ぐらいかな。
もっと書いたような気もするけど。
機能的には、入力ファイルとして当時標準だった Lindo のフォーマットを
扱えるようになっている。また、計算部分も疎行列を fill-in(ゼロ要素が非ゼロ要素になること)が少なくなるように
扱っており、出力もラスターファイルや PostScript で結果を出力できるようになっている。
(出力された図をそのまま Latex に組入れられる)
計算部分のパラメータ数 20 より、実行プログラムのオプション数 23 の方が多い。
自分で言うのもなんだが、結構、多機能である。
計算実験でもスピード、精度ともに、前述の OB1 と互角であった。
(ちなみに、当時 OB1 は AT&T から億単位の値段で売られていたと思う。)
C で書くと何百行になる内点法のプログラムも、Mathematica で書くと
わずか 20行ほどである。当時、C プログラムの検証に Mathematica を使っていた。
このように、スクリプト言語はアルゴリズムの本質を記述するのに便利である。
(Mathematica はスクリプト言語ではないけど)
Mathematica の場合、行列演算が数式そのままに書けたのが便利であった。 00/11/14(火)最近、VC++ を使っていて思うのだが、 正常にコンパイル&リンク終わった後に、 ビルドすると、またコンパイルをする。 小さいプログラムを書いているときに多い。 どうやら、次のようになっているようだ。
00/11/15(水)Ruby によるシミュレーションの話で、 何故、同じ時刻になるのか不思議だったが 原因がわかった。
上のようなプログラムで確認したところ、平均3万2千回ぐらいで、 0 が発生している。約半分のところで出るとすると、1/2^16 の確率だ。 元が short(2バイト)なのだろうか。 どうりで、10万単位時間も流すと同じ時刻が発生するわけだ。 ううむ、どうにかして欲しいなぁ。このままだと、大規模な シミュレーションには Ruby 使えませんねえ。 00/11/16(木)UML について。 最近、周りでも UML を使うようになってきている。 実際にお客さんとの打合せの資料でも積極的に使うようにしている人もいる。オージス総研のセミナーにも数人が参加し、 UML技術者認定制度 のブロンズは大体みんな取った。 次は、シルバーを取っていくことにしている。 でも、部全体でやっていこうとの認識はまだなく、 個人的に広がっているに過ぎない。 それほど難しく考えずに、ちょろちょろと使うようにしていければいいと思う。 00/11/17(金)最近 C++ 以外の話が多いのは、 ネタがないからです。誰か頂戴。っていうより、「C++ プログラミング所感」 改め「プログラミング所感」にします。(2001年予定)
さて、最近ツリー形式のエディタを使うようになったのだが、
結構便利である。
で、思ったのが、ツリー構造っていろいろな所で使えるということである。 C++ の汎用テンプレートツリークラスでも作るかな。時間ないけど。 00/11/20(月)メーラとして、Eudora 使っているのだが、 なんかリソースをバカ食いして困る。 ということで、普段、メーラは起動していない。 Windows 9X を使っているのが悪いと言えばそうなんだけど。 今度から、Windows2000 にするかな。(バグの数は減ったのかな)Linux もいいかな。
それはさておき。サーバにメールがあるかどうか確認する
プログラムを作りたいと思っていた。フリーでも転がっているけど、
自分で書いてみたかったのだが、よくわからなくて調べる暇もないので
ほっておいていた。
ちなみに、頻繁に来る ML は 上を見て分かるように Ruby の ML である。
00/11/27(月)VS-FlexGrid 7.0J の不具合。 パッチVC++ で SIS の OCX を使っていると「デバッグサブシステムの初期化エラー」とか 出たりすることがある。原因はよくわからないが、一度、直接 exe を起動すると直ったりする。 前へ 目次 次へ |