K.Maebashi's BBS

ご自由に書き込んでください。雑談も可。
テスト書き込みの類はテスト用掲示板にどうぞ

[日付順表示] [日付順インデックス] [スレッド順インデックス]

新規投稿 | 開設者ホームページへ戻る | ヘルプ

[773] Re:ポインタ完全制覇
投稿者:yuya
2007/02/20 02:13:25

頭の整理のために、ということで。 A1'. char(*)[10] はchar配列(要素数10)へのポインタ。 char*[10] はcharへのポインタの配列(要素数10)。 A1". char (*hoge)[10]; Q3. 暗黙の変換前は 「charの配列(要素数10)の配列(要素数5)」型であり、 b[0]~b[4]の5つ(配列が5つ)のこと。 暗黙の変換後はその先頭要素へのポインタになるので、 「charの配列(要素数10)へのポインタ」型の右辺値となり、 そのポイント先はb[0]です。 「b + 1」はb[1]を、つまり10バイト離れたところをポイントします。 Q4. &bの「b」は暗黙の変換が行なわれないので、 配列そのもの(この場合は「配列の配列」という配列そのもの)になり、 それに「&」を付けた「&b」は「charの配列の配列へのポインタ」型の右辺値です。 「&b」のポイント先は「50バイト分のヒトカタマリ」であって、 「&b + 1」はもはや配列外をポイントするので、 ここを書き換えると何が起こるか分かりません。それこそ >「配列へのポインタ」で示される先に実体が1つあるだけ という状態ですね。
[この投稿を含むスレッドを表示] [この投稿を削除]
[772] Re:ポインタ完全制覇
投稿者:774RR
2007/02/20 02:13:25

こたえ書いちゃうのは簡単なので、書かずに盛り上げるテスト # この辺、ポインタ中級レベルになるのでしょうか?それとも上級? ここはまず「配列へのポインタ」っつーものがあることを理解しなければならない。 char a[10]; という配列があるとき &a[0] = 配列先頭要素へのポインタ右辺値 (char*) &a = char [10] 配列へのポインタ右辺値 (Q1. この型の表記を行え) である。これも概念レベルの問題なので理解できない人は一生理解しないままだったりする。 # A1.char(*)[10] 型。 Q1'. char(*)[10] と char *[10] の違いを述べよ Q1". char(*)[10] 型の変数を作るにはどう記述するといい? 先の例は「配列へのポインタ」で示される先に実体が1つあるだけ。つまりよくある char c; char* p=&c; と同じレベルの些細でつまらない例に該当する。だからより実用的な例に変えよう。 Q2.char b[5][10]; とするとき &b[0] の型を述べよ (答えは char[10] へのポインタ右辺値) Q3.では b とだけ書いたら何型で何を指す? (暗黙の変換前と変換後の両者を答えよ) Q4.では &b と書いたら何型で何を指す? このへんがすらすら答えられるようになるなら CES さんの問題も簡単。
[この投稿を含むスレッドを表示] [この投稿を削除]
[771] Re:ポインタ完全制覇
投稿者:yuya
2007/02/20 02:13:25

こんにちは、CESさん。面白くてためになる問題ですね。 >関数名、引数、戻り値の要素数は任意とする。 戻り値はポインタなんですよね。 戻り値の要素数というのは? 戻り値であるポインタが指す先のchar型配列の要素数ということでしょうか? それとも、戻り値がポインタの配列になるかも知れないということでしょうか? いや、ポインタの配列を返したくても、その先頭要素へのポインタ(ポインタへのポインタ)を返すしかないわけですけれど(^^;)
[この投稿を含むスレッドを表示] [この投稿を削除]
[770] Re:ポインタ完全制覇
投稿者:CES
2007/02/20 02:13:25

この前、読み方で思いっきり躓いた。 せっかくだからクイズにしてみよう。 問: char 型の配列へのポインタを返す関数の宣言を記述しなさい。 関数名、引数、戻り値の要素数は任意とする。
[この投稿を含むスレッドを表示] [この投稿を削除]
[769] Re:ポインタ完全制覇
投稿者:(ぱ)
2007/02/20 02:13:25

>うーん。なぜ皆「ポインタのポインタ」というのだろうか。すごく不思議。 >「ポインタへのポインタ」というほうがわかりやすくて正確だと思うのだが。 同意します。なので「ポインタ完全制覇」では、ある型Tを指すポインタは、 原則「Tへのポインタ」と言っているはずで…と思い、「ポインタのポインタ」で Googleしたらうちのページがトップに来た Σ(゚д゚lll)ガーン >ポインタで躓いている人の半分くらいはポインタ左辺値と右辺値の違いがわかってない。 一応そのつもりで、ポインタ完全制覇では、ポインタはまず型であり、ポインタ型が あるのだから、ポインタ型の変数もポインタ型の値もある、と書いているつもりです。 K&Rに「ポインタはアドレスを格納する変数である」と書いてあるのがやっぱり 諸悪の根源ではないかと。 今は(例によって)酔っ払っているので続きはまた考えます。
[この投稿を含むスレッドを表示] [この投稿を削除]
[768] Re:ポインタ完全制覇
投稿者:774RR
2007/02/20 02:13:25

うーん。なぜ皆「ポインタのポインタ」というのだろうか。すごく不思議。 「ポインタへのポインタ」というほうがわかりやすくて正確だと思うのだが。 ポインタで躓いている人の半分くらいはポインタ左辺値と右辺値の違いがわかってない。 判ってるほうはわざわざ区別する気にならないのであえて明言しないし。 これは概念理解の問題なので、噛み砕いて説明しても無駄な場合が多くて泣きそう。 残り半分は宣言の読み方が判らないだけで、こっちは技術的問題なので慣れでなんとかなる。 int (*pf)(); と int func(int*); は C++ では明確に非互換なので無問題。 C では JIS X 3010:2003 6.7.5.3 関数宣言子にて「関数型が適合とは」の解説があり、 俺的解釈では pf=func; は適合であるため、「警告が出ないのがあたりまえ」 「もし警告が出るとしたら、それはコンパイラが親切なだけ」であると思われる。 int (*pf2)(void*); と int func(int*); は非互換でなきゃならない。 # 元発言者様の発言が質問になっていないので単なる感想。
[この投稿を含むスレッドを表示] [この投稿を削除]
[767] Re:ポインタ完全制覇
投稿者:(ぱ)
2007/02/20 02:13:25

はじめまして。 >はじめまして、プログラミングを勉強するには遅すぎる年齢(28)で >C言語を勉強している者です。それも再就職を目指し今は病気療養で傷病手当 お大事に。無理せずがんばってください。 >そこで、本の内容で、ポインタのポインタについて一切触れられてない点が気になり >ました。(そんなに、使う物では無いのかな?と勝手に思ってもいますが) ご意見ありがとうございます。 うーん、ポインタのポインタ(型)、というのは、単にポインタから派生したポインタ型に 過ぎませんから、宣言の読み方からすれば3章の記述で足りそうですし、 実際の使い方としては、4-2-2の「可変長配列の可変長配列」および4-2-4の 「引数経由でポインタを返してもらう」で扱っているつもりです。 また、実際にプログラムを書くときの用途もそんなところです。 もちろん、ポインタのポインタを引数で返してもらうときにはポインタのポインタのポインタを 使いますし、ポインタのポインタの可変長配列を作りたければやっぱりポインタのポインタの ポインタを… いやこっちはさすがに構造体を導入すると思いますが。 まあ、「ポインタのポインタって何だ?」と疑問を持った人が、ポインタ完全制覇で その説明を探すときのことを考えれば、章タイトルに「ポインタのポインタ」が 入っていたほうがよかったかもしれません。 >例えば >int (*func)(); >int func2(int*); >とプロトタイプ宣言し、mainブロック内で >func = func2; >とした時僕としては、プロトタイプ宣言時に >int (*func)(void*); なぜ、 int (*func)(int*); ではだめなのでしょうか。 ひょっとして、funcに代入される実際の関数は、 int func2(int*); int func3(char*); int func4(double*); のうちのどれだかわからない、ということでしょうか。 もしそうであれば、これはちょっと邪悪な書き方だと思います。 # 厳密に言うと規格上もまずいです。int*とvoid*が同じ形式とは限らないので。 どうしてもこういうことをしたければ、私なら、 int (*func)(void*); としておいて、関数側で、受け取った後でキャストします。 int func2(void *a) { int *int_p = (int*)a; ... }
[この投稿を含むスレッドを表示] [この投稿を削除]
[766] ポインタ完全制覇
投稿者:負け組一号
2007/02/20 02:13:25

はじめまして、プログラミングを勉強するには遅すぎる年齢(28)で C言語を勉強している者です。それも再就職を目指し今は病気療養で傷病手当 で生活している、ま、いわゆる人生の負け組ですが、一応もがくだけもがこうと プログラミングを勉強しだし、前橋さんの著書ポインタ完全制覇を読みました。 そこで、本の内容で、ポインタのポインタについて一切触れられてない点が気になり ました。(そんなに、使う物では無いのかな?と勝手に思ってもいますが) それと、int (*func)();の様な形でプロトタイプ宣言したときに、引数の型を明示してないですが了解している警告として受け入れてはいますが 例えば int (*func)(); int func2(int*); とプロトタイプ宣言し、mainブロック内で func = func2; とした時僕としては、プロトタイプ宣言時に int (*func)(void*); としときたいのですが、別の著書では、void*を引数にすると厳しいコンパイラーでは エラーが出るとの事で(僕はVisual C++6.0で勉強していて、引数にvoid*を指定しても しなくても、警告もエラーも出ず実行できてしまいました) 引数は省略するほうが良いのだろうと思いつつ 警告でるなら、なんかやだな。とも思うんです。 かといって、引数として受け取る時にキャストする方法なんて知りませんし (ただの勉強不足なのでしょうが)そこら辺をもうちょっと本の中で知りたかったです。
[この投稿を含むスレッドを表示] [この投稿を削除]
[765] Re:オブジェクト指向再入門
投稿者:(ぱ)
2007/02/20 02:13:25

>「疑り深いあなたのためのオブジェクト指向再入門」が非常に為になった。 >有り難うございます。 どうもです。あのページはうちのコンテンツの中でも毀誉褒貶が激しいページなので(w ためになったといっていただけるとたいへん励みになりますです。
[この投稿を含むスレッドを表示] [この投稿を削除]
[764] オブジェクト指向再入門
投稿者:penchi
2007/02/20 02:13:25

「疑り深いあなたのためのオブジェクト指向再入門」が非常に為になった。 有り難うございます。
[この投稿を含むスレッドを表示] [この投稿を削除]
[762] Re:感謝状
投稿者:(ぱ)
2007/02/20 02:13:25

> そうでもないんですわ。push/popなんて命令も用意してもらえてません(T-T) > だいたい、レジスタに格納した番地のメモリにアクセスすると言う命令すらない(T-T) > 固定番地へのアクセスのみです。 ははあ、なるほど。 そうなると、式評価の途中の値は、レジスタに置くか、それが足りなくなったら どこか決めうちのワークエリアに置くことになるわけですね。その場所は、 コンパイル時に決定できますし。 勉強になりました。
[この投稿を含むスレッドを表示] [この投稿を削除]
[761] Re:感謝状
投稿者:本多
2007/02/20 02:13:25

>>っていうか、対象ハードウエア(自社製ASIC)が構造上どない頑張ってもスタック構造を処理できないので(@_@) >すいません、そういうCPUは触ったことないので質問です。 このハードウエアはCPUとは呼べないでしょうね。 今どきのPCの周辺機器のほうがよほど複雑なプログラム実行が出来そうです(^^;) >eval.cの関数をwrite()に置き換えたとのことなので、そこで吐いているのはpush/popだと >思っていましたが、そうでもないんでしょうか? そうでもないんですわ。push/popなんて命令も用意してもらえてません(T-T) だいたい、レジスタに格納した番地のメモリにアクセスすると言う命令すらない(T-T) 固定番地へのアクセスのみです。 だからメモリの上から順にグローバル変数を割り付けていくと言うことしてます。 (っていうか、メモリも2kだけですし...)。 JMP命令はあるけど、固定JMPのみ、というわけで、関数呼び出しも再起呼び出し禁止、 関数の型はvoid func(void)のみって感じになりました。 コンパイラがwrite()で吐いてるのは 「メモリからレジスタ1へのコピー命令(に対応する数値)」とか 「レジスタ aとレジスタ bの演算命令(に対応する数値)」とか 「特殊機能を実行する機械語(に対応する数値)」とかを データファイルに書き出してます。 実際、(PCではないのですが)本当にあるシステムの周辺機器なので メインのCPU(こっちは汎用CPU!)上で動作するプログラムが 作ったデータファイルからデータを読み込んで対象ハードウエアに書き込み、 対象ハードウエアを起動するという形を取ります。
[この投稿を含むスレッドを表示] [この投稿を削除]
[760] Re:感謝状
投稿者:(ぱ)
2007/02/20 02:13:25

>っていうか、対象ハードウエア(自社製ASIC)が構造上どない頑張ってもスタック構造を処理できないので(@_@) すいません、そういうCPUは触ったことないので質問です。 eval.cの関数をwrite()に置き換えたとのことなので、そこで吐いているのはpush/popだと 思っていましたが、そうでもないんでしょうか? それとも、その程度のスタック操作はできるけど、スタック上のインデクシングができないとか、 そういうことでしょうか?
[この投稿を含むスレッドを表示] [この投稿を削除]
[759] Re:感謝状
投稿者:本多
2007/02/20 02:13:25

> 対象とするプログラムが200~300ステップとのことなので、ローカル変数とかは >ばっさり省略されたんでしょうか。crowbar - αだとそんな感じになりそうな気が… はい。ばっさりと。 っていうか、対象ハードウエア(自社製ASIC)が構造上どない頑張ってもスタック構造を処理できないので(@_@) そんなのを作って「C言語のサブセットだ」と言い張るのは少々(だいぶ?)誇張だとは自覚してますが... アセンブラで記述した200~300ステップがC言語で50行以下にできるところが一番の収穫です(^^)
[この投稿を含むスレッドを表示] [この投稿を削除]
[758] Re:感謝状
投稿者:(ぱ)
2007/02/20 02:13:25

> 感謝状なので、わざわざ 丁寧な書き方してみましたが、 > こそばゆかったっすか...(^^;) そりゃもう。 > じゃあ次からは丁寧に書かないようにしますわ(^O^)/ へい。それで結構でございますです。 > crowbar中のeval_...とか、execute_...って関数をwrite()文の羅列に置き換えたので、 > たぶんcrowbarを簡単にしただけです。  対象とするプログラムが200~300ステップとのことなので、ローカル変数とかは ばっさり省略されたんでしょうか。crowbar - αだとそんな感じになりそうな気が…
[この投稿を含むスレッドを表示] [この投稿を削除]
[757] Re:感謝状
投稿者:本多
2007/02/20 02:13:25

>>前略 前橋様 > いつものmanybookの本多さんにこういうことを書かれたとすればさすがにこそばゆいので。 いや、いつもの本多ですが...(^^;) そっか、新しい掲示板はメールアドレスないんですよね。 感謝状なので、わざわざ 丁寧な書き方してみましたが、 こそばゆかったっすか...(^^;) じゃあ次からは丁寧に書かないようにしますわ(^O^)/ >>で一念発起、C言語で書けるようにコンパイラ(もどき)を作ることにしました。 > 正直、機械語を生成するCコンパイラだと、せいぜい構文解析までしか役に立たな >かったのでは、と思いますが、多少なりともお役に立てたのでしたらよかったです。 なにせ、コンパイラを作ろうなどと考えたこともなかったので 構文解析やらなにやらと、全く知らなかったので勉強になりました。 何よりソースコード付で、そのコードを丁寧に説明してあるのが わかりやすくて良かったです。 crowbar中のeval_...とか、execute_...って関数をwrite()文の羅列に置き換えたので、 たぶんcrowbarを簡単にしただけです。
[この投稿を含むスレッドを表示] [この投稿を削除]
[756] Re:感謝状
投稿者:(ぱ)
2007/02/20 02:13:25

>前略 前橋様  はじめまして…でいいですよね?  いつものmanybookの本多さんにこういうことを書かれたとすればさすがにこそばゆいので。 >有益なページの提供に心から感謝します。  や、ありがとうございます。 >で一念発起、C言語で書けるようにコンパイラ(もどき)を作ることにしました。  正直、機械語を生成するCコンパイラだと、せいぜい構文解析までしか役に立たな かったのでは、と思いますが、多少なりともお役に立てたのでしたらよかったです。
[この投稿を含むスレッドを表示] [この投稿を削除]
[755] Re:オブジェクト指向「初」入門
投稿者:CES
2007/02/20 02:13:25

[750] への追補。 > 設計手順としては、まず常識的な is-a を使って継承関係を考えてみた > 上で、それを LSP で検証すればいいんですよ。検証に通らない場合には、 > たとえ常識的なis-a 関係であっても、継承関係にはしません。検証に通 > れば、もちろん継承関係にします。 「常識的な判断」が悪いとは思いません。 ところで、「常識」と「真理」は違います。 日本人の常識とアメリカ人の常識が異なるのと同じように、販売支援ソフトと画像処理ソフトの設計常識が同じはずがありません。要するに、常識ってのはいっぱいあります。 その中から、今回作りたいソフトに適する常識を選ぶ必要があります。 経験に基づいて「常識的な設計手法は、適した設計である可能性が高い」と言うのであれば、俺が言う「いくつもの方法の中から、適したものを選べ」というのと、全く変わりません。 ですから、それを否定する kit さんは、「常識的な設計」すらすることができません。 > これも繰り返しになりますが、「照らすまでもなく」のような暗黙の知識は、 > 法則としては役に立たないんですよ。 では「常識」は暗黙の知識ではなく、明文化されているのですか?
[この投稿を含むスレッドを表示] [この投稿を削除]
[754] 感謝状
投稿者:本多
2007/02/20 02:13:25

前略 前橋様 有益なページの提供に心から感謝します。 最近、自社製の小さなハードウエアに搭載するためのアセンブラプログラムを作る仕事をすることになったんです。 規模がそれほど大きくない(200~300ステップ)のですが、アセンブラはやっぱり大変です。 で一念発起、C言語で書けるようにコンパイラ(もどき)を作ることにしました。 連載「プログラミング言語を作る」の前橋さんの説明とcrowbarソースコードを繰り返し読むことで なんとか2日ででっち上げることに成功しました。 さすがにcrowbarとは欲しい機能とは大分違うのでソースコードをそのまま利用することはしてません(できません)が、このページのおかげで大分楽に作ることが出来ました。 しょせん、ハードウエアの制限でポインタとか文字列とか浮動小数点とか多くの機能が実装しない超簡単コンパイラですし、当面私しか使わないし、出来たコードをデバッグしなくちゃいけないのは変わらないのですが...(^^;) 勉強になりました。どうも ありがとうございます。
[この投稿を含むスレッドを表示] [この投稿を削除]
[753] Re:分析と設計
投稿者:CES
2007/02/20 02:13:25

>>ただ、この手法を更に推し進めるとインターフェイスの多重継承の山になりかねないので要注意でしょう。 >>struct fooable { virtual void do_foo()=0; }; >>struct barable { virtual void do_bar()=0; }; >>struct bazable { virtual void do_baz()=0; }; >>struct hoge : fooable, barable {...}; >>struct piyo : barable, bazable {...}; > >推し進めるというか、どこかで一歩飛んでいる気がします。 >FlyingAnimal は(あるいは、飛行機を統一的に扱おうとしてさらに上位に定義した FlyingObject でさえ)、IFlyable とは一線を画したものであるような気がします。 >なぜそう思うのかは、いまのところ「直感」としか言いようがないのですが。 失礼。 今回は既に bird からしてインターフェイスでしたね。 Java や C# が言うところの「インターフェイス」の意味づけが、自分の中でよくできていない感じです。
[この投稿を含むスレッドを表示] [この投稿を削除]
[752] Re:分析と設計
投稿者:CES
2007/02/20 02:13:25

>ただ、この手法を更に推し進めるとインターフェイスの多重継承の山になりかねないので要注意でしょう。 >struct fooable { virtual void do_foo()=0; }; >struct barable { virtual void do_bar()=0; }; >struct bazable { virtual void do_baz()=0; }; >struct hoge : fooable, barable {...}; >struct piyo : barable, bazable {...}; 推し進めるというか、どこかで一歩飛んでいる気がします。 FlyingAnimal は(あるいは、飛行機を統一的に扱おうとしてさらに上位に定義した FlyingObject でさえ)、IFlyable とは一線を画したものであるような気がします。 なぜそう思うのかは、いまのところ「直感」としか言いようがないのですが。
[この投稿を含むスレッドを表示] [この投稿を削除]
[751] Re:分析と設計
投稿者:774RR
2007/02/20 02:13:25

>「こうもり」を派生したくなった時点で、名前をflying_animalとかに変更するのが筋では? 御意。それは大いにありだと思う今日子の頃です。 今回のこの案件A’A”では、それでうまく行きそうです。みな幸せ。 ただ、この手法を更に推し進めるとインターフェイスの多重継承の山になりかねないので要注意でしょう。 struct fooable { virtual void do_foo()=0; }; struct barable { virtual void do_bar()=0; }; struct bazable { virtual void do_baz()=0; }; struct hoge : fooable, barable {...}; struct piyo : barable, bazable {...}; とてもまともに分析したとは思えない醜い設計です。 抽象度は増したのかもしれませんが、実用度が薄すぎます。まともに使いこなせるとはとてもとても。 # 自己反省をかねて age
[この投稿を含むスレッドを表示] [この投稿を削除]
[750] Re:オブジェクト指向「初」入門
投稿者:CES
2007/02/20 02:13:25

kit さんへのまとめ。 俺は当初、kit さんは「オブジェクトの is-a など問題にしなくてよい。LSP のみが確かな判断基準となる」と言っているのだと思っていました。 理由は、 > 設計手順としては、まず常識的な is-a を使って継承関係を考えてみた > 上で、それを LSP で検証すればいいんですよ。検証に通らない場合には、 > たとえ常識的なis-a 関係であっても、継承関係にはしません。検証に通 > れば、もちろん継承関係にします。 これを [715] まで言わなかったことです。 しかし、実態は上記の通り、kit さんも、LSP を適用する前に、オブジェクトの is-a での判断を行っています。 そして、俺が [722] で書いた > 結局、この文献は「どんなものを継承関係にすべきか」という指針には、一言も言及していないのです(「どんなものを継承関係にしてはいけないか」は、LSP を用いて言及しています。<以下略>)。 これは、kit さんにも当てはまります。 上記の [715] の文章は、まさにこれだからです。 LSP で検証したことによって、最初に下した「常識的な判断」が正解だったか間違いだったかは確かに分かりますが、その「常識的な判断」は LSP を用いて下したものではありません。 kit さんも、「どんなものを継承関係にするのが正解か」は、LSP を用いて判断していないのです(判断しているのは「結果的に正解だったか」です)。 > これも繰り返しになりますが、「照らすまでもなく」のような暗黙の知識は、 > 法則としては役に立たないんですよ。 「照らすまでもなく間違いと分かる」のは「まるで神に囁かれたかのごとく、なんとなくわかる」のではありませんよ。「LSP など適用しなくても、分析をしっかりとやることによって、きちんと根拠付けて導かれる」のです。 もっとも、kit さんが求める「分析というステップをすっ飛ばして、『LSP に照らすまでもなく』正解が分かる、設計のお手本」から見れば、これでさえ暗黙的に見えるかもしれませんけれど。 kit さんの求めるものとは、「多くの場合に適用できる(が、たまには失敗することもある)、常識的な設計パターン」さえ超越した、「常に成功を約束された設計パターン」なのでしょう。すなわち、「あるクラスAは、どのような案件であるかによらず、常に、他のクラスの子クラスではないか、あるいは、Bの子クラスであるかのどちらかである」という保証(と、AとBの一対一の対応表)ですね。なぜなら、kit さんは「案件によって、Aの親はBが適切な場合も、Cが適切な場合もある。それは案件を分析してみるまで分からない」というのではダメだとおっしゃるからです。 そのようなパターンが本当に存在するのなら、それは素晴らしいことでしょう。 しかし、もしそれを実践したとすると、失敗するケースの方が多いように思えてなりません。 > むしろこういう説明は、 > この説明に沿って機械的に作業していけば、「オブジェクト指向設計」ができるんだ。 > という印象を与えかねないという点で有害だと思います。 > 確かに、ソフト屋は常に一定以上の品質のプログラムを供給しなければならないものであり、その意味では、「機械的にやっていけば誰でもちゃんとした設計ができる」という手法があるのならば大変結構なことです。しかし、残念ながら、オブジェクト指向設計はそこまでいっていないと私は思っています ※4。 > ていうか正直私は、こういうことは、未来永劫、決して「機械的にできる」ようにはならないと思います。 「疑り深いあなたのためのオブジェクト指向再入門/なぜわからなくなってしまうのか?」より抜粋。 わかってらっしゃるじゃないですか。
[この投稿を含むスレッドを表示] [この投稿を削除]
[749] Re:分析と設計
投稿者:タイガー
2007/02/20 02:13:25

>>もしくは、間違ってfly()させちゃいけないのなら >>例外を投げるのもありかなと思います。 >これは LSP 違反だと思うのです。 >http://www2.ocn.ne.jp/~yamagu/object/LSP-J.pdf >の Rectangle と Square の例[本当の問題]と同等な問題を内在させることになりそうです。 >さて、こういう場合に「そもそも同一視すべきだったか」を私は問いたいのです。 ここで、前提条件として以下のものを考えます。 「struct bird { virtual void fly()=0; }; に対し、birdを継承したbat(こうもり)クラスの実装も クライアント側の処理もうまくいった。 ここで新たにpenguin(ペンギン)クラス(処理)を追加する必要が 生じた。」 この時の設計として、penguinクラスとbatクラスを 同一視(例えば、両方ともbirdから継承)すべきかどうかを考えてみます。 私の前回の発言のように、penguinクラスとbatクラスを 同一視して、penguinのfly()の実装で例外を投げようとすると、 birdを扱うクライアント側の処理でbatとpenguinを 同一扱いできないので、LSPに違反します。 つまり、penguinクラスのfly()の実装でどうしても例外を 投げたいのであれば、明らかに設計ミスで、birdクラスで 例外を投げるように設計しておかなければなりません。 しかし、penguinのfly()を、batクラスのfly()とクライアント側で 同一視できるように実装しておけば問題ありません (例えば、fly() {}のような空実装など)。 すなわち、774RRさんが、[745]の2でおっしゃってるように、 既に実装されているクライアント側の処理にマッチするように penguinのfly()を実装すれば問題ありません。 しかし、現実的には全てのクライアント側の処理を調べるのは 現実的ではないので、LSPを頼りに、 「penguinのfly()の振る舞いがbatのfly()の振る舞いと同等である」 という「常識的な」判断ができれば、クライアント側でbatとpenguinが 同一視できると判断しても妥当と言えるかもしれません。 また逆に、LSPに違反した継承を行った場合でも、 クライアント側で両方に共通した使い方のみで実装してあげれば 問題ありません (もちろん、その後、LSPに違反するような実装をクライアント側の 処理で行う実装を追加すれば問題が起きるので設計としては かなり危険な設計ですが)。 また、774RRさんの[743]での以下の疑問 >さて、こういう場合に「そもそも同一視すべきだったか」を私は問いたいのです。 は、「既に存在するクライアント側の処理がどうなっているか」と 「penguinのfly()の実装をどうしたいか」の両方に依存しているので 常に正解の設計方法論はないと私は思います (クライアント側の処理をあらかじめ規定するには、Design by Contractとかに 類する機能が必要だと思います)。 ここで、私の出た結論としては、私自身かなり混乱しているのですが、 つまり、penguinクラスをbirdから派生させてbatと同一視すべきかどうか という設計を迫られたとき、これがLSPに当てはまるかどうかを 考えようとして、 「penguinのfly()の振る舞いが、batのfly()の振る舞いと同一か」 という疑問を持ったとき、 penguinのfly()を実装しようとした時に、どう転んでも クライアント側の実装がbatのfly()と同一視しかできない実装になっている のであれば、全く問題ない訳で、 クライアント側の実装により、少しでも同一視できない可能性があるなら そういう設計すべきでない訳で、 つまり、それをどう判断できるかと言うと、クライアント側のコードの 予想がつかないため、そんなに単純にLSPで判断できないような気が してきました(クラスが単純ならできる場合もありそうです)。 ここでは、fly()関数1つのみを考えている訳ですが、実際は、クラスは 複数の関数を持つ場合がほとんどなので組み合わせるともっと複雑になるし。 結局、同一視すべきかは、LSPはかなり参考にはなるが、 どんな局面でも通用する設計方法は存在せず、「実装依存」な気がします。
[この投稿を含むスレッドを表示] [この投稿を削除]
[748] Re:分析と設計
投稿者:CES
2007/02/20 02:13:25

>bird 派生クラスは全て「飛べる」必要があり、飛べないものをこいつから派生したらダメ。 >1.だから、この bird からダチョウクラスを派生させるのはダメ。 >2.もしダチョウクラスを bird から派生させたいのであれば bird の実装を再検討。 >ではどのような bird にすればよいか、は要件に応じて再分析が必要。 >その再分析の際に「特定の設計原理(銀の弾丸)」ってものはありえない。 >ただその分析の結果の実装は LSP に則るべし。 鼻血が出るほど同意。
[この投稿を含むスレッドを表示] [この投稿を削除]
[747] Re:分析と設計
投稿者:CES
2007/02/20 02:13:25

概ね、皆さんに同意ですね。 >struct bird { virtual void fly()=0; }; このクラス設計が意味するところは簡単で「鳥は飛べる」ただこれだけです。 論理学風の言い方をすれば「鳥ならば飛べる」。 >Q1.案件B(あるいはA’と言い換えても)では「こうもり」を扱う必要が生じた。 >基底クラスを書き換える/書き換えない?派生クラスの実装をどうする? 「飛べるならば鳥である」ではありませんから、コウモリを鳥のサブクラスにすることはできません。 どうしても統一的に扱いたいのならば、Flying-Animal クラスでも導入すべきです。 >Q2.案件C(あるいはA”)では「ダチョウ」「ペンギン」を扱う必要が生じた。 >基底クラスを書き換える/書き換えない?派生クラスの実装をどうする? これは、前提条件が崩れているわけですから、理想的には一からやり直すべきです。 鳥クラスの下に「飛べる鳥」クラスと「飛べない鳥」クラスを設けて、fly メソッドは飛べる鳥クラスに移動する…と、既存の bird.fly メソッドを使っているコードは動かなくなりますね。 コスト的にやり直しが許されないのならば、飛べない鳥クラスの fly メソッドは、何もしない空実装にするのも手かもしれません。
[この投稿を含むスレッドを表示] [この投稿を削除]
[746] Re:分析と設計
投稿者:のぐー
2007/02/20 02:13:25

>struct bird { virtual void fly()=0; }; オブジェクト指向ぜんぜん分かってない者ですがひとつだけ。 そもそも、birdって言う名前をつかうこと自体、おかしくないですか? 「こうもり」を派生したくなった時点で、名前をflying_animalとかに変更するのが筋では? そしたら「ダチョウ」を派生できないことは明らかなはずで。
[この投稿を含むスレッドを表示] [この投稿を削除]
[745] Re:分析と設計
投稿者:774RR
2007/02/20 02:13:25

んと、他人の意見だけ募集して自分の意見を出さないのはアレげなので私の見解。 LSP=同一基底クラスから派生したオブジェクトは、振る舞いが同一であると契約する 契約=派生クラス実装者と基底クラス利用者の間で取り決め ってことなので、そもそも struct bird { virtual void fly()=0; }; って設計した以上 bird 派生クラスは全て「飛べる」必要があり、飛べないものをこいつから派生したらダメ。 1.だから、この bird からダチョウクラスを派生させるのはダメ。 2.もしダチョウクラスを bird から派生させたいのであれば bird の実装を再検討。 ではどのような bird にすればよいか、は要件に応じて再分析が必要。 その再分析の際に「特定の設計原理(銀の弾丸)」ってものはありえない。 ただその分析の結果の実装は LSP に則るべし。 ってことなんですけど。
[この投稿を含むスレッドを表示] [この投稿を削除]
[743] Re:分析と設計
投稿者:774RR
2007/02/20 02:13:25

>ダチョウ、ペンギンを例外にするのは良くないので、 >上記の実装なら同一扱いできるので良いと思います。 そこまでは限りなく同意なのですが >もしくは、間違ってfly()させちゃいけないのなら >例外を投げるのもありかなと思います。 これは LSP 違反だと思うのです。 http://www2.ocn.ne.jp/~yamagu/object/LSP-J.pdf の Rectangle と Square の例[本当の問題]と同等な問題を内在させることになりそうです。 さて、こういう場合に「そもそも同一視すべきだったか」を私は問いたいのです。
[この投稿を含むスレッドを表示] [この投稿を削除]
[742] Re:分析と設計
投稿者:タイガー
2007/02/20 02:13:25

面白そうなので勝手に考えてみました。 初期の設計として私なら以下のように考えます。 >Shapeにdraw()を付けたら、その時はうまくいったとして。 > >Q1-1)機能拡張でShapeをグループ化できるようにしたとき、ShapeGroupクラスは > どこに位置づけるべきか? > >Q1-2)機能拡張で「補助線」機能を付けた。補助線は、描画中にだけ使用する機能で、 > 後工程に回らないからデータとしてファイルに保存しない。 > >A1-1(前橋版) >ShapeGroupをShapeのサブクラスにして、draw()をオーバーライドしてその >グループに含まれているShapeのdraw()メソッドを再帰的に呼んでやれば >描画はできますが、それはやっぱりアレなのでShapeのスーパークラスとして >Visibleとかの抽象クラス(またはインタフェース)を作り、ShapeGroupとShapeは >そのサブクラスかなあ。 >たとえば「color」というフィールドは、ShapeGroupには要らないだろうし。 これは、私も(ぱ)さんと同様にします。 つまり、ShapeGroupとShapeを共にVisibleから継承させて、 ShapeGroupにVisibleのリストを持たせます。 そうすればCompositeパターンになり、描画するときは、 トップからつながってるShapeを全て描画できます。 >A1-2(前橋版) >補助線がShapeのコレクションに入らないとすれば、完全に別扱いかなあ。 >もちろんVisibleを導入し、Visibleのコレクションを作って、Shapeは >ShapeコレクションとVisibleコレクションの両方から参照される、という >構造でもいいけど、両方のコレクションをメンテするのも面倒だし。 Visibleのサブクラスとして補助線クラス(AuxLine)を追加します。 AuxLineには、Visibleを集約させて(持たせて)、実際はShapeクラスを 持ちます。必要な機能をShapeから委譲させます。 結局、新たなクラスをShapeのサブクラスにするのは、 結合度が強すぎてガチガチになるので、 ゆるやかな結合で設計しておいた方が良いのかと思います。 これだけの情報だと情報量が少ないですが、 もっとうまい設計があったら教えて欲しいです。 >>Q2.案件C(あるいはA”)では「ダチョウ」「ペンギン」を扱う必要が生じた。 > >具体例が思いつきませんが、「描画されないShape」ですよね。 > >void draw() {} ダチョウ、ペンギンを例外にするのは良くないので、 上記の実装なら同一扱いできるので良いと思います。 もしくは、間違ってfly()させちゃいけないのなら 例外を投げるのもありかなと思います。
[この投稿を含むスレッドを表示] [この投稿を削除]