第88回 C++の仕様なんかいな!(継承について考えない) 99/11/25
もう来年の2月にはいまの会社をやめて、東京に行くことになっています。自分の部屋(3つある)には、書籍が凄い量あるので、それをどうするか画策していたのですが、いい方法を思いつきました。
1.ダンボールに整理する。(立つ鳥跡を濁さず)
2.デジカメにすべてのダンボールの中身の写真をとっておく。(えっ?なんで?)
こうしておけば、必要になったとき、「ちょっと、7番のダンボールの左端から4つ目にある本と、右から3つ目にある本送ってもらえんかなー」とうちの母親に電話すれば送ってもらえるというカラクリである。(なんやうちの母親は電子書庫かいな…)
さて。前々回の内容について、ねここしゃんという方から、こんなレポートをいただきました。
そういえば、スーパープログラマーへの道の第86回で書かれていた継承した場合のオーバーロードの問題について興味があったので、いろいろ調べてみました。 手持ちのコンパイラすべてでエラーになるようです。 VisualC++のヘルプ(Ver.5)によると、クラススコープを越えるオーバーロードはできないというのが仕様のようです。Microsoft独自とはなっていなかったので、C標準だと思います。 あった方が便利だと思うし、実装もそんなに難しくないと思うのですが、なぜこんな仕様になってるんですかね? にしても、今まで何故この事に疑問を持たなかったんだろう? 意外と、実際のプログラムではこういったコードになる事は少ないのでしょうか? |
要約すると、C++標準化委員のほうに足を向けて寝なさいと、そういうことですかね。(何がどう要約されとんのや!>俺)
西遊記さんからは、こんなメールが。
BC++でも同じようにコンパイルエラーでました。どーもC++の仕様のようです。Delphi(てゆーかObjectPascal)では可能なのでC++のやつには一切の言い訳は認めれん!(ら抜き) > C++の仕様だとしたら、C++標準化委員の方に足を向けて寝ることにする とゆーわけでANSIだかK&Rだかしんないけどコヤツらに十六文キックだ!(延髄斬りでも可) # 個人的には引数の違う関数に継承するっちゅー機能は、絶対必要とも思えないんですが |
うーん。つまりは、ANSIとK&Rもたこなぐりか。(それじゃ意味わからんで>俺)
その他にも同様の趣旨のメールをいくつかいただきました。(この場をお借りして、感謝の意を表します)
すべてに共通して言えることは、
・なぜ出来ないのか疑問である。
・しかし、必ずしも必要ではないのではないか?
というのが骨格にあるようですね。まー、私もそう考えている一人なのですが…。また何かわかったら、ここで書きたいと思います。
しかし、いまさらながらに思うのですが、ひょっとするとC++ってのは、かなりダサいのでしょうか?湘南の冬の海で「あたし、C++やってる人とはこれ以上、付き合えない!」なんて彼女に言われる日も近いかも…。(んなわけあるかい!) あるいは、南紀白浜で女子大生かと思ってナンパした未亡人から「私の前の旦那は継承がとっても好きな人だった…」だなんて遠くの海を眺めながら言われたりしてな。(なんの話しとんじゃ>俺)
第89回 Singletonについて考える(デザインパターン論考1) 99/11/29
プログラムには、形というものがある。こういうことをすることは、この形、こういう処理をするときは、この形といったパターンがある。一般にそれは“デザインパターン”と呼ばれ、C++なんかのテンプレートよりも、もっと抽象度の高いものだ。(というか、C++のテンプレート機能=型汎用性ぐらいの機能では、こういったパターンについて記述することは出来ない)
まあ、そんなことを言っていても始まらないので、手始めにSingletonというパターンについて考える。これは、何かというと…説明は面倒なので少しテキストから引用させていただきますと…
あるクラスに対してインスタンスが1つしか存在しないことを保証し、それにアクセスするためのグローバルな方法を提供する。 ◎ 動機 クラスにインスタンスが1つしか存在しないことが重要になる場合がある。たとえば、システムには多数のプリンタを接続することができるが、プリンタスプーラは1つでなければならない。同様に、(以下、割愛) ◎ 実装 Singleton パターンを利用する場合に考慮すべき実装上の問題をまとめておく。(中略)Singleton クラスは次のように宣言される。 class Singleton { public: static Singleton* Instance(); protected: Singleton(); private: static Singleton* _instance; }; 実装は次のようになる。 Singleton* Singleton::_instance = 0; Singleton* Singleton::Instance () { if (_instance == 0) { _instance = new Singleton; } return _instance; } |
だ、そうである。(デザインパターンについてご存知ない方のために、ひとこと言っておきますと、デザインパターンとはあくまでパターンであって、このコードを直接的に継承等で再利用しようというのではありません。ある関数にsingletonの機能をつけたければ、このように書いてはどうですか?というぐらいの意味合いのようです)
ところで赤字の部分は、NULLと書いたほうが良いような気がしますが、そんな細かいことはさておき、この実装のダサさときたら!そもそも、ここで確保された_instanceはいつdeleteされるんでしょうか?“パターン”と言うならば、デストラクタで
if (_instance!=NULL) delete _instance;
の一行ぐらい入れておいたほうがいいんでないでしょうか?
まあ、デザインパターンというのは、実装レベルよりむしろ、概念レベルに重点を置くようなので、やねうらおがそんなことを言ったところで、「実装への批判は概念への批判とはならない」という逆批判の憂き目に逢着するのが目に見えていますが、それでもあえて言わせてもらいますけどねー、こんなダサイ実装しか出来ないからこんなトロコイこと言うてるんでないんですか?実装だけで言えば、関数内のローカルスタティックオブジェクトを使って、
class X { public: static X& GetTheX(void); }; X& X::GetTheX(void) { static X x; // local static object return x; } #define TheX (X::GetTheX()) // もし必要ならば、TheX.Func();というように使う |
と書けばそれで終わりでしょう?これなら、初めて呼び出されたときにインスタンスが作られ、C++の終了処理のなかで自動的にデストラクトされますよ。(これをデザインパターンへの批判という気はさらさら無いですよ…)
まあ、デザインパターンは、再生産性の向上のためにあるのではなく、開発のために必要な方法論として捉えられるのでしょう。しかし、はっきり言ってやねー、このへんがデザインパターンを提唱している連中の甘いところでやねー、なんでそれを言語レベルで実装しようとしたりしないのかっちゅーのが、やねうらおの疑問ですよ。デザインパターンとして提唱されているいくつかのパターンについては、確実に言語レベルで実装可能やないのん?上のSingletonについても当然言語レベルで実装できますし、簡単な歴史刈る穴らいザ(なんちゅー変換しよんじゃ!)を用意するだけで可能です。もし、自分たちの提唱するパターンが普遍的なものだと信じるのならば、それくらいのプリプロセッサぐらい作るべきじゃありませんか?有益な概念を単なるオブジェクト指向設計のための方法論にしておくのはあまりにも、勿体無いような気がしてなりません。
第8A回 カブト虫さんと遊ぼう(謎) 99/11/30
仕事の事情で、今日は、ひさびさにゆっくり起きた。テレビをつけるとガッチャマンの再放送をやっていた。寝ぼけ眼(まなこ)で見ていたのでよくわからなかったのだが、悪の組織みたいな奴らがゴキブリ型の巨大ロボットでサトウキビ畑を襲撃していた。あなた、サトウキビ畑ですよ、サトウキビ畑。ガッチャマンの司令部でそのアナウンスが流れると、「奴らはなんでそんなことをするんだ!」と主人公らしき奴が言った。そうだそうだ!なんでそんなことをするんだ?興味津々でテレビのほうに耳を傾けていたら、なんとそのアナウンスによると「甘いものを民衆から奪い、不安にさせ、混乱を巻き起こし、その瞬間に民衆を叩きのめす作戦に違いない」のだと。
えっ゛。なんで!?
おもわず鼻から牛乳出しそうになりましたよ!甘いものがないと不安になるのか。わてらは、カブト虫かい!そして、それを聞いた主人公(たち)が、
「そ、そうか!!」「そんなことは、させないぞ!!」
えっ゛。こいつら納得しとんか!?
まったくもってやねうらおには理解不能である。
そんなことはどうでもいいのだが、前回掲載内容についてある人からご指摘を受けました。
内容は、デザインパターンの思想理念から、ローカルスタティックオブジェクトというような言語固有の機能(C++とその流れを汲む言語にしかないような機能)に依存すべきものではない。この意味で俎上に載せたコードのほうがローカルスタティックオブジェクトを用いた(やねうらおの)実装よりも優れたコードであると言えるのではないかと。
はい。大筋において、それは正しいと思います。
しかし、その本は、C++での実装とSmallTalkでの実装、そしてCD付録ではJavaでの実装が掲載されており、それぞれの言語機能に強く依存した実装がされていることを言い添えておきます。(C++の仮想関数,多重継承,メンバ演算子->のオーバーロード等など) この意味で、そこで掲載されている実装は、説明のためというより、ある程度の実用性のあるコードのようです。(少なくとも著者はそう信じているでしょう)
そして、もうひとつ。状況に応じて生成するインスタンスを切り替える必要がある場合(たとえば環境変数の値に応じて生成するインスタンスを切り替えるような場合です。あるときにはSingletonの派生クラスSingletonA、あるときにはSingletonBのインスタンスを生成してSingletonにダウンキャストして返さなければならないような場合です。こういうことがしたいことは往々にしてあります)、前回、僕が説明したやりかたでは出来ません。その意味において、ローカルスタティックオブジェクトを使用しないほうが、“汎用性”があるのだと。そういうことは、言えるかも知れません(僕としては、そっちのほうに突っ込んで欲しかった)
まあ、デザインパターンというのは追求すれば奥が深いです。実装方法うんぬんはあくまで二義的なもので、その提唱されている概念の善し悪しや有用性を議論するのが先決のような気もしています。(もちろん、有用とわかった概念は実装についても議論するに値します)
第8B回 継承について教えられる(続OOP講座) 99/12/1
DirectX7の新機能でキーボードの排他的アクセスっちゅーのがあります。私は2年ほど昔、あるDirectXのメーリングリストでキーボードの排他的アクセスは出来ますか?という間抜けな(?)質問をして、
「そんなことが出来たらソフトが暴走したらどうしようもないじゃないか!」だとか
「Windowsはゲームマシンではない!」だとか、
「歯が痛いなら歯医者に行け!(それは歯痛)」だとか、
再起不能になるまで、たこ殴りにされた経験のあるやねうらおですが(笑)、どうもDirectX7ではそれが出来るようです。正直、ええんかいなーとか思っておりますですよ、はい。
今回は、第86回の内容について、こんなメールをいただいたので、掲載させていただきたいと思います。
やねうらお様 はじめまして。「スーパープログラマーへの道」の第86回からのC++の仕様云々のことに関して思うところがありましたのでメールを送らせていただくことをお許しください。って送ってしまってから言っても仕方ないですが(^^; C++ の仕様であるという指摘は既にあるようなので繰り返しませんが、私はその仕様を悪いとは思っていません。というのは、あのような書き方は B では Getkey(void)を隠すためによく使われる方法だからです。たとえば class 人 { public: int お願い(内容); }; class エライ人 : public 人 { public: int お願い(内容, 袖の下); }; エライ人のところには手ぶらでお願いしに行ってはだめだと、あるいは手ぶらで行く場合には 人::お願い(内容) を明示的に呼び出すようにしたいと、そういった意味でこのようなコードを書いたりします。 私はデフォルト引数を使うか、関数名を変えるかのどちらかで対処することがほとんどです。たまたま値が不要なだけで概念としては存在してもいいのならば前者を採用する し(例えば 人::お願い を int お願い(内容, 袖の下 = 0)にする)、引数が違うということで関数の役割や概念が違うのであれば後者です(エライ人::お願い は 人::お願いの継承にしておいて int 内緒のお願い(内容, 袖の下) を作るとか)。 第86回のコードについて言えば class B { public: int GetKey(void) {return A::GetKey();} int GetKey(int) {return 0;} }; あるいは main() 内での呼出を b.A::GetKey(); とすることで望み通りの結果が得られるかと思います。もちろん、GetKey(void) と GetKey(int) が同じ概念を表すのでなければこれ以前の話として名前を変えますが。 アマチュアの身で生意気を申しましたが、私のプログラミングのスタイル(ってほどのもんかい)だとこうなるという例として御笑覧いただければ幸いです。 ------------------------------------- 中西 渉(わたやん) 名古屋学院高等学校 mailto: watayan@meigaku.ac.jp |
まあ、見る人が見ればわかると思いますが、このわたやんさんは確固たるプログラミングスタイルを持ってらっしゃるし、説明のわかりやすさもピカイチです。名古屋学院高等学校の学生さんなのでしょうか?教員のかたなのでしょうか?どうも後者のような気がしますが、もし前者だとしたら卒業後、是非、うちの会社に来ていただきたいものです。(こんなところで勧誘するなって…)
これから私が言うのは、この意見に対する反論ではありません。私の単なる疑問や、感想と捉えていただければ結構なのですが、C++的な継承というメカニズムはある程度規模の大きいソフトウェアを作ろうとすると、すぐに破綻すると思うのです。それはまあ、ActiveX(OLE2)の誕生の歴史でもあるので、ここで詳しいことについては説明しません。
別の言い方をすれば、まったく継承を使わずにプログラムすることは可能だと思うのです。具体的には、基底クラスにする代わりに、オブジェクトを自分のクラス内に取り込み(メンバ変数として持ち)、必要ならばインターフェイスはすべて自前で用意(定義)するのです。こうして、クラスをcomposite(合成)していけば継承しなくてもなんとかなります。インターフェースを用意するのは、あくまでそのクラスの実装者の責任というわけです。(この点、わたやんさんの主張に共通したものがありますね)
「そんなことをしたらめんどくさいよー!」という声が聞こえてきそうですが、それは、そこに用意されている開発環境の問題だと思います。そのへんがビジュアルに、クリックひとつで全選択できて、public継承みたいなことが出来るならば何の問題もないかも知れません。
そう考えていくと、わたやんさんの指摘にあるように「あのような書き方は B では Getkey(void)を隠すためによく使われる方法」というのもなんとなく理解できます。ひょっとすると、これはC++のpublic継承では基底クラスのpublicメンバすべてがpublicになってしまい、その一部だけをprivateに出来ないというのを間接的に補うために用意された仕様なのかも知れませんね。(なんか釈然としませんが…)
第8C回 継承について教えられる2(続OOP講座) 99/12/5
魅惑のWindows2000ですが、Professional版の定価が38,800円とのことです。Windows95/98ユーザーにとって、Windows2000でないと動かないソフトがあるわけでもないし、この価格では、Windows3.1→Windows95のときのような大幅な移行は考えられないのではないでしょうか。ちゅーことは、DirectX7が標準になるという話もどことなく雲行きが怪しくなってくるのであります。やねうらおは死ぬまでDirectX3をやってろってことでしょうか。(誰もそんなこと言ってないって…)
えっと。前回の内容について、いろんな方からアドバイスをいただきました。
C++的な継承の利点(というか本来の狙い)は、差分プログラミングなのだと思います。派生元のクラスからの変更点だけを書くことで、すべてを書かずに済むのだと。概念としての継承には問題ないようですが、C++の実装の仕方には、疑問であるというというのが前回までの内容です。この実装について、armさんからこんなメールをいただきました。
VC5.0のヘルプを読んでいたところ、funcという関数の例が出ていました。 グローバルスコープの void func(int) と void func(char *) があったとき、 関数の内部で func(char *) を宣言すると、外部の func はスコープから 外れるので、オーバーロードも無効になるというものです。 (これはCのスコープの考え方として順当なところでしょう) で、C++のクラス継承も、「子クラスのスコープ」というようにCのスコープの 考え方に基づいているので、親クラス(上位のスコープ)のオーバーロード関数 などは「名前解決の方針の上で呼べてはおかしい」ということになるようです。 CとOOPの多重継承の産物であるC++ですが、Cを強く継承したため、 OOPから来た多義性はヨソモノ扱いなのだと思います。 Cの子はC、ということなのですね。 |
なるほど。C++的な継承の実装は、Cのスコープの概念に基づいているようです。第8B回のわたやんさんのメールにあるように、これはこれで便利なこともあるのだと。
ところで、継承をせずに、メンバ変数として自己のクラス内部に取り込み、インターフェースは自前で用意するというスタイル、OOPの世界では「委譲」と呼ばれているようです。COMの世界では、aggregation(集積?)なんて呼ばれてますね。インターフェースは自前で用意と言えば、COMと親和性の高い、JavaのInterfaceも確かそんな感じだったように思います。
OOPと言えば、一般にEncapsulation,Polymorphism,Inheritanceの3本立て(来週のサザエさんか!)ですが、このInheritance(継承)の一形態として、Aggregationも含ませて考えたほうが良いのかも知れません。んがくっく。
第8D回 遠藤君RETURNS(伝説の男ふたたび) 99/12/9
僕の最大のライバルに遠藤君というのがいる。先日、梅田の紀伊国屋でばったり出会った。高校のときの友人だが、お前よくそんな学力でこの学校、入れたなーと教室中の誰もが思わずにはいられない奴であった。
彼は、学校でも数々の伝説を残した。英語の時間、先生に「遠藤君、桃は英語でなんというんや?」と尋ねられて彼は間髪入れずに「ネクター」と真顔で答えた。「どアホ、それはジュースやないかい!」
しかしこの程度は、彼にとっては日常茶飯事である。次の時間も「遠藤君、形容詞にlyがついて副詞になるんや。ちょっとlyのつく副詞、思いつくのをあげてくれへんか」と言われて、彼は、いきなり「ブルーベリー」ときたもんだ。「遠藤君、そりゃ食いもんやないかい!」
その遠藤君はいまや無事に(?)大阪大学を卒業し、大手企業の研究所で研究開発をしているそうである。あんとき、遠藤君の256倍は勉強の出来たはずの(?)、やねうらおは、すっかり駄目人間と化し零細企業でプログラミングなんぞに従事しているんだから、世の中、わからないもんだ。
さて。今回は、staticな関数の是非について考える。staticな関数からなるclassを用意して(あまり意味はない。単なる種類分け)、そいつらを呼ぶようなプログラミングスタイルをとることがある。これらの関数の内部で使用する変数をメンバ変数として宣言していた。staticな関数内で使用されるメンバ変数なので、当然staticな変数である。こいつの初期化をそのclassのコンストラクタで行なっていた。
この場合、コンストラクタが呼び出されるためには、インスタンスが生成されなければならない。当然である。一例としては、グローバルスコープ内にダミーで変数宣言をしておくべきである。当然である。しかし、そんなことはすっかり、忘れていた。結局のところ、初期化はされずに使っていた。なんか呼ぶたびに数が変わりよんなーとぐらいしか思っていなかったが「モーニング娘みたいなやっちゃな!」とあっさり流していた(気付けよ!)
まあ、ケアレスミスなのだが、問題はこれにとどまらなかった。続きは来週の週刊少年サンデーで!(こんなん本気にする奴いたらどうすんねん…)
第8E回 可変長配列について(プリプロセッサによる解決/序) 99/12/13
いまどき固定長配列というのは、とってもダサいような気がする。「モーニング娘は8人まで」だとか「ぽちょむきんは4096隻まで!」みたいな制限は、マシンも速く、メモリ使い放題に近い現在のパソコンにあってはならない制限ではないかと思うときもある。
perlやawk、LISPなどのようにテキスト整形を得意としている言語は、たいてい(メモリの許す限り)無限に入るバッグ(≒コンテナ≒コレクション)を持っている。逆にC言語やJavaでテキスト整形がやりにくいのは、そういったものを言語機能として保有していない(少なくともfirst class objectではない)ことも一因として挙げられるだろう。
そこでプリプロセッサ等を作成し、可変長配列をSTLのvectorを用いて実装することを考えてみよう。
いま仮に、配列添字の省略された配列宣言は、可変長配列とみなすことにする。
user_type ident[];
// 添え字なしの配列宣言は、
→ vector<user_type> ident; // と置き換えると約束する
ついでに、この要素すべてにアクセスするための操作子があったほうが便利に決まっている。VBライクにfor_eachを用意して、
X x[]; // という可変長配列宣言に対して
for_each(e in x)
print(e); // という感じで、すべての要素をprintできるとする。
言うまでもなく、これはvector::iteratorで実装できるから、
for_each(e in x)
→for(vecotr<X>::iterator e=x.begin();e!=x.end();e++)
として(forにしているのには深い意味はない。iteratorのループはwhileで書くほうが一般的かも知れない)続いて、この変数eのスコープに対してe→*eと置換すれば良い。これにより、先のプログラムは、以下のように置換される。
vector<X> x;
for(vector<X>::iterator e=x.begin();e!=x.end();e++)
print(*e);
ただ、この置換がプリプロセッサ的に処理するときに、なんともやらしい気はする。xの型名が必要なのも同様によろしくない。(C++はRTTI(実行時型判別)より、むしろこちらのほうを先になんとかして欲しかったという気がする)
C++の参照&を使って、これをうまく回避できれば良いと思うのだが、参照演算子&には、初期化子がないといけないので、そういうわけにもいかない。
思うに、参照と言っても実体はポインタと同じなのだから、この初期化子が必要という制限をとっぱらってしまって、かつ、参照型のxに対しては、(&x)としてその実体であるポインタを用いることが出来るならば、上のfor_eachは、もっと簡単に書けそうな気もする。このようになっているならば、先のfor_eachの置換例は、
→for(vecotr<X>::iterator &e=*(x.begin());(&e)!=x.end();(&e)++)
と書けて、変数eのスコープに対しe→*eと置換する作業は不要になる。
しかし、欲を言えば、これではeの変数スコープに問題があって、
for_each(e in x) print(e);
for_each(e in y) print(e);
というようなプログラムを書いたとき、eを二重に宣言していることになる。プリプロセッサで置換したときに{ }で囲ってしまう手もないではないが、これもなんだかやらしい気がする。
たいてい初心者のうちは、よくforの初期化式のスコープに悩まされる。
for(int x=0;x<MAX;x++) { ... }
とやっていて、これをコピー&ペーストしようとしてコンパイルエラーを出してしまう。それが嫌ならば、
{ for(int x=0;x<MAX;x++) { ... }} // 箱入り娘:p
としておけば良いという話もあるが、そんなプログラム見たことがないし、わざわざやる価値があるわけでもない。
そのようなforを新たに_forとしてdefineできれば良いのだがCのプリプロセッサのパワーでは、それも出来ない。(と思う)
同様に、このようなfor_eachを言語機能として実装するのは、コンパイラ実装者にとってはたやすいことだが、単なるC++ユーザーにとっては簡単に手が出せることではない。こういった拡張をユーザーに許すためには、C++のプリプロセッサの強化をする必要がある。
そこで、いまdefine拡張構文を考える。#defineで書き始めて:=が行末にあるものは、この拡張構文が適用されるものとする。さきほどのfor_eachならば、
#define "for_each" "(" var$1 "in" var$2 ")" statement$1 :=
"{ for(" classnameof(var$2) "::iterator &"
var$1"= *(" var$2 ".begin()) ; (&"
var$1 ")!=" var$2 ".end();(&" var$1 ")++)"
statement$1 "}". // classnameは、その変数のクラス名を与える
とすればOKである。少し見づらいが、何をやっているかは、ここまで読んでこられた読者諸氏ならば、なんとなくご理解いただけるだろう。前述の可変長配列宣言も、以下のようにdefineすれば良い。
#define typename%1 ident%1 "[" "]":=
"vector<" typename%1 ">" ident%1.
ただしVC++ではユーザー型のコンテナ生成には問題があるから、これだけではまずい(→第7C回参照のこと)
そして、このようなdefine拡張構文で何でも出来るかというとそうでもない。また、他人が見てわかりやすいソースになるわけでもない(ような気がする) そんなわけで、単なる夢物語なのでありました。
第8F回 for_each STL関数について(プリプロセッサによる解決2) 99/12/14
最近、プログラム関係のことでメールをいろんな方からいただきます。なかには、凄腕の方も多数おられて恐縮しています。特に、VC++であろうがPROLOGであろうがLISPであろうが、どこまでもフォローしてくださる方、凄すぎです。ちゅーか、「あなた一体誰?」(C)ハナテン中古車センター)と時々、思わずにはいられません。きっと、その筋でも有名な方なんでしょう。今度プロフィールください。>心当たりの方々(笑)
ときに、前回の内容について、北Cさんから、こんなメールをいただきました。
STLを使うのなら #include <algorithm> のfor_eachがあるからそれ使うってんじゃだめなんすか? for_each( x.begin(), x.end(), print); って風に。VB使ったことないので、良くわかりませんがfor_each(アルゴリズムの) でまずい場合もやっぱあるんでしょうか? |
ご指摘の通り、確かにalgorithmとしてfor_eachというのが用意されています。前回の私の例題がまずかったかも知れません。たとえば
for_each(e in x)
print(e);
ではなく、
for_each(e in x)
e.print();
だった場合、どうでしょうか?これをSTLのfor_eachで簡潔に書けるでしょうか?
わざわざeをprintするだけの関数を作る必要があるのではないでしょうか?(私は、それしかやり方を思いつきません)
だとしたら、ここに来て、STLのalgorithmの弱点を垣間見ることが出来ると思います。
すなわち、(ご存知のように)たいていのalgorithmのSTL関数は、述語(predicator)などとして関数ポインタを必要とするということです。
もちろん、これはSTLの実装に問題があるわけではなく、C++ではそうするのが一番てっとり早い方法だからです。対比してみると面白いと思うのですが、LISPでは関数閉包と言って、関数本体を持ち歩くことが出来ます。(持ち歩くというのは、他の関数に引数として受け渡したり出来るという意味です) これは、LISPでは関数そのものがs-式すなわちLISPにとってfirst class objectだからですが、C++ではこれが出来ません。速度面を考慮すれば出来ないことそのものが短所というわけでもないのですが、STLのalgorithmのようなものを実装しようとするとき、この関数閉包という手段が採れないとすれば、関数ポインタを渡すような実装にするのは当然と言えます。
しかし、関数ポインタを渡してしまうと不利なことも出てきます。たとえば、
int n=0;
for_each(e in x)
e.SetValue(n++);
と同じ意味のことをSTLのfor_eachで行なうにはどうすれば良いでしょうか?
というか、STLのfor_eachを使うと、かえって難しいことにお気づきになられるでしょうか?あえてやるとすれば、
int n;
void _SetValue(type_of_e e){
e.SetValue(n++);
}
としておいて、
n=0;
for_each(x.begin(),x.end(),_SetValue);
とやることです。すでに問題が何であるか、おわかりになられたかも知れませんが、こういうつまらない関数をいちいち作らなくてはならないというのが不満の一つ。これでは非常に見通しが悪くなりますし、その関数内からしか呼び出さないような関数は、関数内関数(PASCAL的な)として書ければ救いもあるのですが、あいにくC++にはそういう手段が用意されていません。
そして、関数呼び出しが介在するので、変数スコープに気を使わなければならないという不満もあります。ここでは変数nをfor_each呼び出し元と、void _SetValue(type_of_e)の両方から見えるスコープ(この例ではグローバルスコープ)に配置しなくてはならないということです。
こういうのが原因で、私個人としては、やったらと関数ポインタを要求されてはたまらないというのがSTLのalgorithm全般に対する不満に直結しており、STLのalgorithmはわざわざ利用する価値がないものがほとんどだと思わざるを得ません。(反論等、お待ちしています)
その解決手段の一つとして、for_eachを言語機能として実装できないか?あるいは言語機能として実装できない(行なわない)ならば、プリプロセッサ的に処理できないか?というのが前回のテーマだったわけです。
あと、前回の内容について、佐波(さ〜)さんから、こんなメールをいただいたので紹介します。
for文のスコープですが, Metroworks Codewarriorではfor文で定義した変数のスコープは for文内だけになってます.(謎) おかげで,Visual C++やgccで書いたプログラムをCodewarriorに 持っていくとボロボロになってしまいます.しょうがないので, もう使ってません(^^; 本当の理由はコマンドラインからコンパイル できないからなんですが. |
やってくれるなぁ(笑)>Code Warrior
気に入った。ざぶとん一枚。