1999年11月の日記。

1999年11月03日

りゅうさんからやねうらお氏が訳の分からないことを書いていると教えてもらったので見る。

今回は「第7F回 錬金術について考える(謎) 99/10/28」だそうだ。

読めば解ると思うけど、ぼろぼろである。代入は無から有を産み出す、とかいう感じの論旨を展開している。

x = y;

というC言語のプログラムがあるとする。これは、言うまでもなく代入を意味する。

はて、これはプログラムといえるのだろうか。

少なくともC言語のプログラムというからにはmain()が無いとおかしい。

この代入という行為が無から有を生み出すカラクリその1なのではないかと

とあるが、まずコレが怪しい。コレについては後に回す。なにしろ他の部分がスゴイのだ。

よく考えてみて欲しい。代入は、日常生活で出てこない概念である。財布から金を払えば、財布の中身は減る。財布の中を変化させずに、お金だけを外に出すことが出来るというのが、この代入という行為なのだ。実は、とっても不思議なのだ。

よく考えてみて欲しい。代入と支払いというのは比喩としてふさわしいだろうか。

どう考えても比喩としてふさわしくないのだ。

「代入」、これは、直接的には、値をコピーすることを意味する。コピー元は副作用を受けない。つまり上の例ならばyの値は変化しない。xにyの値がコピーされる。

ここまではいい。

このコピーを作るというのと支払いというのを同一視していいのか、という問題である。

代入する、ということは代入元と同じものを構築して、それを代入先に格納するわけだ。

むしろこれは設計図を見て新しいものを組み立てている、というようなものであり、支払いとは全然関連が無いように思われる。

むしろ、工場などのほうかふさわしいのではないだろうか。C++でメンバにポインタを含むクラスの代入演算子なんかを定義したことがある方ならばなんとなく理解していただけるのではないかと勝手に期待する。

なぜこんなに代入にこだわるかというと、代入が関数性をぶち壊す要因たりえるからである。


int f(void){
    static int x=0;
    return x++;
}

まずこれだ。関数性というのは、入力に対して出力が決まっているという数学で言う関数の性質だと思われる。

だとすると、確かにこの関数は関数性がない。

なにしろ入力が無いのだ。

しかしこれは代入のせいで関数性がぶち壊れているのだろうか。つまり、代入のせいで入力が無くなっているのだろうか。

単に引数が無いという設計上のミスでしかないと思う。

さらに付け加えるならば、この関数には代入が無いのだ。

static int x=0;まずはコレだが、これは初期化である。代入では無い。C言語を少しかじった人ならば理解できると思う。int a;a=0;int a=0;は意味が違うのだ。もし解らないならconst int a;a=0;const int a=0;というようにconstをつけてみればいい。

次にx++;だ。これはインクリメントであり、代入とは違う。

では調子に乗ってどんどん行こう。

void f(void){
    for(int x=1234567;x;x--) ;
}

このプログラムの停止するか?

main()が無いから…というのは置いておこう。

そもそも停止以前に実行されるのか?

これは単にxを1234567回参照してデクリメントするだけで、他の副作用が全くない。

これがどういうことかというと最適化で削除されるのだ。私が使っているコンパイラは、以前にも書いたがWATCOM C++という割と昔に発売されたコンパイラだ。何しろWindows 98の発売以前に発売されたくらいで、やねうらお氏が使っているWindows 98より後に発売されたMicrosoft製のコンパイラでは日本語化されていないドキュメントが日本語化されているくらい古いのだ。

このような古いコンパイラでも当然のように削除するのたから氏が使っているMicrosoft製のコンパイラなら当然のごとく削除するものと思われる。

無いものは実行できない。実行されないから停止もしない。氏の言うとおりである。

まあ、正の整数から1引く作業を繰り返すわけだから、いつか0になって停止するのだが

あれ?

…まあ、見なかったことにしておこう。

これは、代入が生み出す悲劇であり、数学的にクリーンな議論をしにくくさせる要因でもある。

では、x--がx-=3ならどうか?これを、実際にプログラムを実行することなしに判定することが可能だろうか?実際、1234567は3の倍数ではないから停止しないのだが、

(中略)

これは、代入が生み出す悲劇であり、数学的にクリーンな議論をしにくくさせる要因でもある。

まあとにかく、代入のせいで実行結果が解りにくい、という論旨だと思う。

さて問題。問題なのはどの代入だろうか。

とか書くと悪質な引っかけ問題になる。何しろ代入なんて存在しないのだ。

取り敢えずもう一度(といってもコード部分のみ)引用しよう。

void f(void){
    for(int x=1234567;x;x--) ;
}

さきも説明したが、int x=1234567;というのは初期化であり、代入では無い。

x--は単なるデクリメントであり、代入でもない。

何処にも代入など無い

のだ。

では代入のせいで実行結果が解りにくいのか? 代入など無いのだから代入は関係ないのだ。

x-=3;なら、という事も書いてある。これなら代入である。しかし、演算結果がどうなるかというのは事前に解り得るのだから解りにくいというのも程度の問題でしかない。解らない人なら

int main(void){
    printf("hello! world.
");
    return 0;
}

だって解らないのだ。

代入結果や減算結果が解らない、というのは単に能力不足なだけである。

さて、後回しにした無から有を産み出す、といういうやつ。

a=b;としたときに、確かにbの値は複製されている。一見何も無いところからbの値がもう一つ出てきているように見えるかもしれない。

さて気になるのはaだ。もともとaに格納されていた値はドコにいった?つまり、単にaの値と引き換えにbの値を産み出したとも言えるのだ。

無から有どころか時間と電力を費やして値を変質させているわけで、全然不思議でもなくなってしまう。

「何を馬鹿なことを言ってるんだ」と思うか、「へーこんな考えかたもあるのか」と思うかは個人の自由だが、出来ることならここから何か発展させて欲しいと思う。

しかし、氏の思惑に乗ってしまったのか、ついつい発展させて考えてしまった。

1999年11月08日

東芝ユーザーズフォーラムで、なぜLibretto ffは売れなくてVAIO C1は売れるのかというような発言があった。Libretto ffに新型が出たのにもかかわらず全然会議室で話題にならなかったのだ。

VAIOが売れるのはSONYだから売れる、という趣旨の発言すらあったがいくらなんでもそりゃないだろうと思う。

少なくともVAIOという商品がそれなりに価値があるから売れるのだ。SONYだからVAIOが売れる、というのであれば東芝だからLibrettoが売れないということでどうやっても勝てない理屈になる。

いや、もとの発言者の方が熱烈なSONYファンで、どうやってもSONYには勝てないと思っているならそれはそれで自由だけど。

そもそも、新しいLibretto ffが話題にならないのは、ユーザが求めている機種では無かったという、それだけの事ではないだろうか。

Libretto ffが出た時にも結構批判的な意見が多かった。ffのようなパソコンを求めているユーザは少なかったと思われる。それがちょっとくらいスペックアップしたところで全然変わらないのだ。

そもそも今までのLibrettoが作ってきた市場に応えるような機種が無くなってきた。

Libretto 20から60までは、とにかくどこにでも持ち運べるフルスペックPC、という感じのコンセプトだったと思う。必要最低限の機能だけに絞ってとことんまで小型化したPCが 出来上がった。かばんに突っ込んで何処にでも持っていけるWindowsが動くマシン、ソレがLibrettoだった。

これは何処にでも持っていきたいユーザにはヒットした。他に物が無いのだ。コレしかない。

CEやPDAではできないことややりにくいことというのはどうしてもある。

こういうことを出先や外出先でもやりたい、デスクトップが無いところでもやりたい、というニーズは確実にある。

Librettoが築いた市場というのは、こういうターゲットだった。

とにかく持ち運んで使うことを第一に考えてあるPCって、Libretto 60、Libretto SS 1010、FIVA、FMV-6300MC4/Wではないだろうか。

FMV-6300MC4/Wはちょっち大きいし重いけど、電源が取れないところで使うにはあのバッテリーは魅力。逆にアレだけ長時間使えるならちょっとくらい大きくてもいいかなと。

可動時間が長いということは出先で長時間使えるというわけで、出先で使うための条件を満たしているともいえる。

他のマシンは充分に小さくて、軽い。持ち運んで使える。

しかしLibretto ffはこういう、とにかく持ち運んで使うユーザにはいまいち魅力が無いわけだ。だからこういうユーザは買わない。

Libretto 70では持ち運ぶことを優先するために削ってしまった性能を取り戻して、その代わりに可搬性を削ったモデルとしてLibretto 100がある。その結果として大きくなって重くなってしまった。こうなるとやはり持ち運んで使いたいという人には不評だった。

逆にLibretto 70では機能を削りすぎて使いにくい、という人には売れた。というのも当時は他にソレしかなかった。

Librettoが築いた市場というのは楽に持ち運べるPCの市場だ。Libretto ffが比較的持ち運びやすいとはいえLibretto SSより持ち運びにくいのはこの市場では受け入れられなかった。

しかし、それより大きいPCの市場、遊べるパソコンとしての市場ではすでにVAIO C1があった。

なぜC1は売れるのか。SONYだから売れる、という人もいる。

しかしVAIO C1シリーズが売れているのはSONYだから、という理由では無い。

いくらなんでもユーザがSONYというブランドだけで買うほど盲目とは思えない。同じように東芝だから売れないとかLibrettoだから売れないとも思えない。

そもそもC1を買うユーザとLibrettoを買うユーザというのはあまり重ならないのだ。

C1も、もう少し軽くなってバッテリーが持つようになれば、持ち運んで使えると思う。こうなればLibrettoが作った市場と重なってくる。Librettoとユーザが重なってくる。

しかし、今のC1だとまだ何処にでも持っていって使うというレベルには達していないと思う。まずバッテリーが持たないので外出先で長時間使うというのはつらい。

Librettoのユーザと違ってC1のユーザというのは、あまり外で使わないのだろう。

VAIO C1やVAIO 505は持ち運んでドコでも使うパソコンという感じがしない。

505は、何処でも使うというよりはどこでもいっしょ、持ち運ぶことに意義があるというか所有する楽しみがあるパソコンだと思う。

C1はただのパソコンではなくて、遊びに使えるパソコン。

コンセプトが違うのだからユーザ層も違ってくる。

特にC1の「遊べるパソコン」というの見せ方がうまいし、ポイントも見た目で解りやすい商品だ。パソコンなんて難しい、と思ってしまう人には「遊べるパソコン」というのはか なり魅力的に映ると思う。これなら遊べると思うのではないか。20万円というのは高価だとは思うが、普通のパソコンではなくて遊べるパソコンなのだから妥協の余地がある。

それに比べてLibretto ffはカメラが付いたとはいえC1、Let's note commに続く3番煎じでしかない。その上、広告でもC1に比べてもいまいち押しが弱い。

C1はカメラに加え、そのカメラをどのようにして使うか、というところまで同時 に提案してソフトなどいろいろ付けて提供している。ソレに比べて、LibrettoやLet's note commはカメラが付いているというだけでその先がない。ココがも弱い。

SONYだから売れるのではなくて、C1の使い方まで提案しているからこそ売れたのだ。

それにパソコンとして考えてもC1とLibretto ffでは比較にならない。私もLibretto ffとC1のどちらを買うかと言われればC1を買う。Librettoが壊れた時に真っ先に購入候補に挙がったのはC1だった。バッテリーが持たないのが致命的でFIVAに流れたのだが。

なにしろLibretto ffとC1では重さはそれほど変わらない。しかしLibretto ffは解像度が低い。どうせなら広いほうがいい。

持ち運ぶことを考えたらLibretto SSとかモバイルパックの方が使い勝手がいいし、バッテリーの持ちを考えればFMV-6300MC4/Wも捨てがたいし、カメラとか見た目とか解像度ではC1が上。Libretto ffはどう使うにしても中途半端だ。どのマシンにも勝てる部分が無い。

最近のLibrettoが話題になりにくいのは、メインとなるユーザ層を捉えきれていないためだと思います。誰が買うのか、誰に売るのか。

C1ユーザに売りたいのならC1よりC1らしい遊べるパソコンにしないとダメ。でないとC1を買うに決まってる。

Libretto SSユーザに売りたいのならLibretto SSと同等の持ち運びやすさでないとダメ。ちょっと持ち運びにくくするしてもCPUを速くするとかHDDを大容量にする、メモリの最大搭載量を増やす、バッテリー駆動時間を伸ばす、という携帯パソコンとしての性能を追求しないとダメ。カメラなんてものは誰も望んでいない。

MP3の再生だってカメラだってCEでできてしまう。だったら軽くて安いCEを買うに決まってる。

ターゲットを決めて、そこにアピールできるような宣伝ができればLibretto ffでも売れるかも知れない。しかしいまいちカメラも液晶も売りがありません。これじゃ売れるわけがない。

1999年11月14日

またもやねうらお氏がスゴイことを書いていた。

思わずチャット中に「スゴイ事書いてます」と言ってしまったら「第何回ですか?」ということを訊かれたのだが、他の人からすかさず「全部」と言われたのには参った。

いや、確かに凄いのだが。

今回のは「第82回 Encapsulationについて考える(続OOP講座) 99/11/13」ということでカプセル化、隠蔽化についてのコラムだ。

カプセル化というのは、データを直接いじらせないようにする仕組みである。直接いじってしまうと間違ったデータを設定されてしまうかもしれないからだ。

たとえば時刻を記憶する変数(データ)があるとする。となると0:00から23:59以外のデータは設定できないようにしたほうがいい。設定できるとバグの原因になりかねないからだ。

そこで直接時刻データを扱わせずにクッションを置いて間接的に扱い、クッションのところでデータの整合性を取ったり、エラーチェックを行うわけだ。

他にもAが0の時にはBを0にはできない、とかそういう他のデータとの整合性を取るためにも使われる。たとえば戦士が寝ている時には攻撃コマンドは使えない、とか。状態を示すデータが睡眠中を示している時にはコマンドに攻撃を代入できない、という仕組み。

C++ではコレを実現するためには関数経由で値をセットするようにする。

戦士.コマンド設定(攻撃);という感じだ。

中では

switch(引数){
case 攻撃:
    if(攻撃可能){
        コマンド=引数;
    }else{
        return 使用不可能;
    }
    break;
case 魔法:
    if(魔法使用可能){
        コマンド=引数
        使用呪文=呪文選択();
    }else{
        return 使用不可能;
    }
    break;

以下省略

というようなことをするわけだ。

これが直接コマンドを設定するようだと攻撃不可能な状態でも攻撃コマンドが設定されてしまうかもしれない。

こういうことをさせないためにカプセル化を行うわけだ。

それが

bool* XYZ::GetMember(void) { return &m_bMember; }

とやっておき、XYZ型の変数xyzに対して、

*xyz.GetMember() = true;

*そのクラスの変数名.Getメンバ変数名() = 値;

コレでは意味がない。

いじるデータそのものを渡して好きなように弄らせてしまうのではまったく意味がない。ナンセンスだ。

関数経由でアクセスしている分カプセル化しているように見えるが、見えるだけで単に余計な関数呼出しのオーバーヘッドを作り出しているだけで無駄なだけである。

C++の継承は好きじゃないとやねうらお氏は言うが、こういう発言を見ると単にC++の使い方、オブジェクト指向が理解できないだけではないだろうか。理解できないというと語弊があるなら、現代のプログラミングに向いていないというか。

1999年11月15日

久しぶりにモスバーガーに寄る。実は外出中に雨が降りだしたので雨宿り。

カレーチキンフォカッチャとかいうのが出ていたのでさっそく注文。

1999年11月17日

ここ何年か投票用紙が届かないという原因不明の現象を解決するため区役所に行った。

とりあえず住所変更をし直せと言われたのでし直した。

転入日というのが3年前なのでその日付を書いたら

もっと早くだしてください

と言われる。それ以前に、出した書類はちゃんと処理してくれ。こっちも3年前に手続きはしてある。

1999年11月18日

たびたびスゴイことを書いてくれているやねうらお氏のこのコーナー

前回のカプセル化の時には

そんなわけで、次回は、この問題を掘り下げて考えていく。

と締めくくっていたので、あの状態からどう掘り下げるのか非常に興味があったのだが、なぜかWindows 2000β3の話題である。

カプセル化を掘り下げていくとWindows 2000になってしまうのだろうか。理解不能というか。

1999年11月19日

カプセル化というのはデータに対するアクセスを制限するための手法。データをカプセルに入れてしまい、直接扱えないようにする手法のこと。

これが定説。

で、データが扱えないと困るので扱うための関数を用意してアクセスするための手段を提供する。

こうすることでアクセスに制限を設け、好き勝手にアクセスされない、安全なデータを作ることができる。

C++というのは厄介な言語でアクセス制限には大雑把に言って

の2種類しかない。

読み込みはできるけど書き込みはできないという設定はできなくて、全てできるか or できないか しかない。

つまり他からアクセスできるようにした瞬間、それは書き込みも可能になってしまうわけだ。

たとえば預金残高というデータがあったとする。他の人に教えることも無いような気もするが、あるとする。

C++では、これが外部からアクセスできるようになっている場合、書き込みも可能になってしまう。

class FOO{
public:
    double 預金残高;
};

だとFOO びい; びい.預金残高=0;というように平気で預金残高を変えられてしまうわけだ。

コレじゃ困る。困るならどうするかと言うと書き換えられないようにする。C++ではアクセス制限は全てか無しかどちらかしかない。

class FOO{
private:
    double 預金残高;
};

これで勝手にアクセスできない。アクセスできるのは自分だけということになる。

これだとFOO びい; びい.預金残高=0;というように変えようとしてもコンパイルでエラーになるから安心だ。

しかし、コレでは他から預金残高を知ることができない。どうしよう。どうしようってどうもこうもないのだけど、

class FOO{
private:
    double 真の預金残高;
public:
    double 預金残高(){
        return 真の預金残高;
    };
};

とでもしておいて、預金残高を知りたい人は預金残高()を呼び出してもらうことにする。

これなら真の預金残高を書き換えられることも無い。

この預金残高()の関数内でなんだかんだすれば、読み込みに対してはカスタマイズできる。

ちなみに、doubleではなくてdoubleの参照を返すように作るとどうなるか。

つまりdouble 預金残高();ではなくdouble &預金残高();とするのだ。

こうすると、真の預金残高の値、中に入っている数値が得られるのではなく、預金残高を格納してある変数そのものを得ることができる。

変数が得られる、ということは結局、真の預金残高の変数に対してアクセス可能になる。つまり書き換えることが可能になってしまうのだ。

また、参照を返す場合は値という抽象的なものを返すわけではないのでいいかげんなものを返してはいけない。

参照を返すようにした場合。return 0;というように、値は返せないのだ。参照を返すと言う場合、変数への参照を返すわけであって、ソレは数値そのものではいけない。

また、いいかげんな変数を適当に返すのも良くない。たとえばローカル変数への参照を返した場合、ローカル変数というのはそのブロックを抜けた時点で消滅する。つまり関数から抜けた時点では消滅しているハズだ。

こうして得られたローカル変数への参照を用いてアクセスすると、すでに存在しない変数に対してアクセスしようとするので、異常な動作を示す。

とまあ、簡単にすませたけど予備知識。簡単でもないけど。

さて本題だ。

そんなわけで、次回は、この問題を掘り下げて考えていく。

とやっておいて次回ではなくて実は次々回だったというフェイントが入っていたやねうらお氏のこのコーナー

待望の次回がやってきたとまたもりゅうさんから教えていただいたのでチェック。

いま、仮に、プロトタイプ宣言で引数を(avoid)と書いて、関数呼び出しが省略できるように、C++の規則を拡充すれば、前々回の最後のスタイルは、

bool& XYZ::GetMember(avoid) { return m_bMember; }

とやって、

xyz.GetMember = true;

と、あたかもメンバ変数のごとく使用することが出来る。

しかも、間には関数呼び出しが介在しているので、この代入は関数側でカスタマイズできる。

まずコレを検証する。

なんらかの関数、func()があった場合、この関数を呼び出すにはfunc();と書く。括弧が必要なわけだ。この括弧が無い場合には関数呼び出しにはならず、関数へのポインタになる。

func=valueと書いた場合、このfuncは関数へのポインタとして解釈さてしまうのではないだろうか。

つまりやねうらお氏が書いたあたかもメンバ変数のごとく使用するように書いたら、コンパイラからはあたかも関数へのポインタのように見えてしまうと思うのだ。

拡充された文法と既存の文法の区別が曖昧なので判断できないのではないか。

仮にコレが括弧を付けるとかなんかの方法で区別で切るようになったとしよう。

次はしかも、間には関数呼び出しが介在しているので、この代入は関数側でカスタマイズできる。という問題。

本当にカスタマイズできるのか?

結論から行くとまず不可能。

まず、とただし書きしたのは一応可能な方法があるから。バックグラウンドでメモリを監視して、変更された時に処理する、という手段があるがかなり非現実的である。

では何故不可能だと判断したか。

参照を返すということは変数を返すということだと書いた。

つまり、その変数に対してのアクセスは普通の変数に対するアクセスと変わりがない。何もカスタマイズできないのだ。

参照を返す物を使ってびい.預金残高()=0;と代入した場合、

処理呼び出し元クラスの中
参照を取得びい.預金残高()の呼び出し呼び出されていないので何もできない。
参照を取得呼び出し中なので何もできない。
びい.預金残高{    return 真の預金残高;};
参照に代入びい.預金残高()=0すでに関数から抜けている。

という感じで、クラスの中からでは代入に対しては処理できない。代入しているところで処理しないといけないのだ。これではなにもアクセス制限ができない。

さっきの預金残高の話で行けば、預金残高に対して無制限のアクセス権を渡すのと同じ話になる。つまり、預金残高を教えてくれと言ってきた人にカードと暗証番号を渡して自分で調べてこいというのと同じ。

これがいかに馬鹿げているかというのは充分に解ってくれると思う。値を返すというのが預金残高の数値を返すということで、参照を返すということは預金残高を記憶しているものを返すわけだ。

ダミーの変数を返してごまかす、というのも先程書いたようにローカル変数を返すようではメモリの内容を破壊しかねないし、だからといってダミーの変数をあらかじめ確保しておくというのも馬鹿げている。また、関数を抜けてしまえばその後は何もできないのでダミーの変数に対して書き込みがあったことを検知することはできない。ダミーの変数に書き込まれた値を反映させることはできないわけだ。

さらに言うと、これでは変数への代入が成功したかどうかを知る術が無い。つまりエラーチェックができないのだ。エラーチェックができないということは異常な動作をした時に対する対応ができないわけ。

もう、落ちるところまで落ちるしかない。

そもそもエラーチェックをしようと言う発想が無いのかもしれない。そういう人が書いたプログラムと言うのは非常に危険だということを最近痛い目にあったので痛感している。

やねうらお氏のカプセル化というのは典型的な手段と目的を取り違えているパターンでしかないように見える。

つまり、普通はアクセス制限をするためにカプセル化するわけだが、この場合カプセル化するためにカプセル化している。アクセス制限をするつもりが無いのなら最初からカプセル化せずにグローバル変数でもなんでも使えばいいのだ。

1999年11月23日

気がついたらこのファイルも80KBをこえているという体たらくで、さすがに大きすぎるのではないかと。とりあえず分割。

最近はやねうらおウォッチャーと化してきたような気がしないでもないが、またもネタにするやねうらお氏のこのコーナー

今回のは「第86回 継承について考える(続OOP講座) 99/11/22」だそうです。

問題になっているは関数の多重定義。継承とは何の関係も無い。

まずは氏が問題にしているコードを掲載しておこう。

class A {
public :
    int GetKey(void) { return 0; }
};

class B : public A {
    int GetKey(int) { return 0; }  // これが悪さしているようだ
};

main() {
    B b;
    b.GetKey();    // ((A)b).GetKey();なら可能
}

つまり、サブクラスのB側でGetKey()を再定義しているためにスーパークラスのA::GetKey()を呼び出せなくなっているという話。

まずこれは関数のスコープ(名前が見える範囲)が違うので仕方がない。一言で言えはC++の仕様である。

なぜこういう仕様なのか。これは割と簡単と言うか単純な理由。内側のスコープで再定義した場合、外側のものは見えなくなってしまうのだ。これはC++に限らずC言語からの仕様。

え? C言語には継承なんか無いって? 確かに無い。そもそもこれは継承とはなんの関係も無い次元での話なのだ。

たとえばこんなC言語のコード。

int foo;
int main(void){
    int foo;

    foo=1;
}
void bar{void){
    foo=1;
}

この場合ファイルスコープでfooというintの変数が定義されている。しかしmain()の中でも同じ名前の変数が定義されている。

別に同じ名前の変数があっても構わない。これはスコープが違うため、両者は区別できるからだ。

main()の中でのfoo=1;で使われるfooはどちらのfooか。

これはmain()の中で行われることなのでmain()のスコープで解決される。つまりmain()の中にあるfooが使われる。

もしmain()にfooが無ければ、その外側のスコープのfooが使われる。

barでの代入はbarのスコープから解決される。barのスコープにはfooは定義されていないので外側のスコープ、ファイルスコープで宣言されるfooを用いる。

つまり、再定義した場合は外側のものを隠すことができる。

これはC言語ではあたりまえのことであって、このことが構造化プログラミングを強力にサポートしている。

もしこの機能がないと、関数内で変数を定義する時にこの名前はすでに使われているかどうかをすべてのモジュール内を確認しないといけない羽目に陥る。コレでは多人数で開発するのは非常に困難になるし、一人でも混乱の原因になる。

この、再定義することで名前を隠蔽できる、という機能は効率的に開発するためには必要なのだ。

では継承した時には何が起きているのか。

話は単純で、サブクラス側で同名の関数を宣言したことで、スーパークラスの関数を隠した、というだけのことだ。

たとえば

class 車{
public:
    void ターボボタン();
    void アクセル();
    void ブレーキ();
    void 右旋回();
    void 左旋回();
    void 後進();
    void 前進();
}

というクラスがあったとして、これからブレーキの壊れたダンプカーを派生させる。

class ブレーキの壊れたダンプカー : public 車{
}

まあ、差し当たってはコレでもいいのかもしれない。

しかしこのブレーキの壊れたダンプカー、このままではブレーキをかけることができてしまう。できてしまうという言い方はどうかと思うが。

int main(){
    ブレーキの壊れたダンプカー ダンプ;
    ダンプ.ブレーキ();
}

というコーディングが許されてしまうのだ。

しかし、ブレーキを使えなくしてしまえば、このようなコーディングはできなくなる。すでにあるものは取り外せないので、使えなくするには隠すしかない。

class ブレーキの壊れたダンプカー : public 車{
private:
    void ブレーキ();
}

とブレーキを再定義しておけば、このクラスではブレーキはprivateになるので外からはアクセスできない。

外からアクセスできるのはpublicなもの(関数、変数)だけ。privateやprotectedなものにはアクセスできない。

コレでブレーキが使えなくなる。

int main(){
    ブレーキの壊れたダンプカー ダンプ;
    ダンプ.ブレーキ();
}

今度はコンパイルエラーになる。そう、ブレーキが使えなくなるのだ。これでブレーキは壊れた。

再定義する事によって隠すと言うことは、このように有用なのだ。

実際にブレーキを壊す、隠すような必要性があるかどうかはともかく、既存のクラスに対して操作の一部を制限する必要があることはある。

廉価版のカメラにはズームが無かったり、操作が簡単になるなどのメリットがあるわけだ。オートフォーカスの機能を追加してマニュアルフォーカスは使えなくする、それと同じように、クラスにも制限を加えることによって得られるメリットもある。

さて、やねうらお氏のところでは何が起きているのか。

class BでGetKey()を再定義したために、class AのGetKey()が隠されているのだ。

呼び出しているのはclass Bの変数を用いて呼び出しているので当然、適用されるスコープはclass Bの物である。しかしclass BのスコープにはGetKey(int)しかない。なのでGetKey()とは結びつけられない。

もし、これがclass Aのスコープにclass Bのスコープを追加する仕様ならこういうことは起きなかったと思う。

しかし、それでは困ることもあるのだ。

class 殴る人 : public 人{
    void 殴る(人 目標, int 殴り方);
}

と言うように殴る人クラスを作る。この殴る人というのはたとえば

殴る人 ゆきちゃん;
ゆきちゃん.殴る(木村, 正拳突き);

と言うように殴られる人と、殴り方を指定して使う。

さて、誰を殴るか、どうやって殴るかを考えるのが面倒になってきたとしよう。

しかしゆきちゃんは特殊な例なので、殴る人とは分けておきたい。殴る人クラスからゆきちゃんクラスを作ろう。

class ゆきちゃんクラス : public 殴る人{
    void 殴る();
}

これでゆきちゃんクラスの人はいちいち誰を殴るか、どうやって殴るかということを考えなくてすむ。

というか、考えてはいけないのだ。

もし、考えて ゆきちゃん.殴る(中嶋, 袋だたき);などと書こうものならコンパイルエラーになってしまうのだ。

コレこそ真のゆきちゃんである。このようにスーパークラスの関数を再定義することで使用できないようにする、ということは有用なのだ。

え? 考えた場合にはどうしたらいいのか? 真のゆきちゃんなら考えてはいけないのだが…。とにかくこうすればいい。

void ゆきちゃんクラス::殴る(人 目標, int 殴り方){
    殴る人::殴る(目標, 殴り方);
}

そう、改めて殴る人のスコープの殴る()を呼び出せばいい。

1999年11月30日

FPROGORGの1番会議室の話題。ホームページをHPと表記することの是非についてという感じの話題が私の中ではものすごく盛り上がっていてめちゃくちゃ楽しい。

そもそもホームページをHPと表記すること自体もアヤシイというか通じない可能性が高いわけで、誤解を避けるならホームページのことをHPとは表記しないほうがいい。

HPと表記されていると、読んだ方としてはHPとはなんだろうと思うわけだ。個人的にHPと称されるものはHewlett-Packardくらいしか知らない。なにしろWelcome to HPなんて書いてあったりするし。

もう一つFNHPというのもあるけど、コレが通じる人と言うのはあまりいない気がする。

とにかくHPと言われると

の内のどれだろうかと悩む。

読んだ方がこれらのメジャーな表記を乗り越えてホームページという日本ではここ1年くらいに使われだしためちゃくちゃ新しい単語を連想してくれるかというと疑問を感じる。

そもそもホームページとはなんなのかという疑問すらあるわけだがこれはさておく。

普通は、省略語が現れた時には元の単語から想像して、一番該当しそうなものを選択してそれに置き換えると言う思考がなされていると思う。

MSのOSと言った時のMSはMicrosoftだし、連邦のMSといったときにはモビルスーツと解釈するように。

だからHPと書かれていてもホームページと解釈できそうなものはある。

しかし、くだんの人はHPにファイルを転送と書いていらっしゃったわけで、このHPをホームページと解釈すると意味が通らない。

ホームページを転送することはあってもホームページに転送するなんて想像を絶する。

当然会議室にも書いたわけだけど、ホームページを転送するならともかく、ホームページ転送するというのは想像を絶するものがある。個人的には理解の範疇を超えているというか。

私の場合、ホームページはFIVAのHDDにあるHTMLファイルをホームページに設定してある。このページ自体はただのHTMLファイルだから転送することができる。

しかし、このHTMLファイルにファイルを転送するとなるとどうやって転送したらいいのか理解できないわけだ。

プログラマーズフォーラムの話題でありながらMSと書いたら蕎麦粉を連想したり、ホームページにファイルを転送する人がいるわけで頭が痛くなる。

っていうか、風邪ですか。御意。


トップを狙う

Copyrigiht © 2002 びい