PPPUC++#11
9. Technicalities: Classes, etc
"Remember, things take time." デンマークの格言にこのT.T.T.があるそうです。どんなに時間がかかってて自分が間違った道を歩んでるように思えても、どんなに自分が前に進んでいなくて愚鈍に思えても、歩むことをやめないことだ、と。何かをなすには時間がかかるもんだ。僕もそう自分を納得させてのんびり(違
さて、言語系話パート2です。クラスです。
前章でもちらっと出ていたDateクラスを例によって少しずつ拡充していくことで、クラス設計のコツみたいなものを伝授しようという目論見
9.1 User-defined types
- char, int, doubleとかの組み込み型を超えるときは今
- UDT, UDT!
- 標準ライブラリ群もUDTの中にはいるのね
- 実際本書でも「ほとんど組み込み」みたいだけど、それでも他のユーザ型と同じネジクギでできているから、としている
- 標準ライブラリ群もUDTの中にはいるのね
- そもそも、なんでUDTなんているのか
-
- 言語設計者が全てを用意することはできないから,,,,,というのはまあそりゃそうですが
- 結局、「アイディアを直接コード中に表現できるから」、なのです
- UDTがあることでアイディアの振る舞いを定義し、それを使って僕らの思考を表現することができるのです
-
- この「型」のおかげで以下の二つが明確になります
- 表現:オブジェクトを表現するのに必要なデータ。状態
- 操作:オブジェクトに適用できる操作
- 多くのアイディアは、大抵この2つ概念パターンに当てはまります。
- C++はこの概念パターンの表現方法のために、2つの機構をもっています
- class
- enumeration
- 何か独立した「モノ」を想定できる場合は大抵それはクラスで表現できるし、すべき
- クラスは基本要素になっている
9.2 Classes and members
- class ∋ {組み込み型、他のUDT、関数}
- こーゆーのをメンバ、と呼んでいる
- 様々な型のメンバがありえる。データ型、関数メンバ、クラス
- メンバへのアクセスは
. のようなドット表記
まあ眠いですね。もすこしはしょりましょうか
9.3 Interface and implementation
- I/Fと実装。分離重要
- I/Fはユーザが直接触る部分
- 実装はI/Fを通して間接的に触る部分
- public:, private:で表現
- ユーザとは誰かね? というとクラスを利用するコード、すなわち定義のある階層
- structとclassの話。そういえば昔こんなことあったなあ・・・[book][これはひどい]Intel スレッディング・ビルディング・ブロック入門
9.4 Evolving a class
- dateいきます
- どうかく?
9.4.1 struct and functions
- 「日付を表すんでしょ? これでいいんじゃ?」
// Simple Date (too simple?) struct Date { int y; // year int m; // month int d; // day };
- 「おう、充分じゃないですか。はい完了。おつかれさまでしたー」
- .....ではなくて
- これだと間違えやすいしわかりにくいし全然便利じゃない。負の値とか有り得ないものをどうやって処理するのか?
- 負なんか判りやすいけどうるう年とかは?
- プログラマだけ頑張ってないでコンピュータも仕事汁!
- 次に考えられるのは共通操作に対してヘルパー関数を用意すること.
- これで何度も同じことをしなくて良くなるし、バグも見つけやすい
- 初期化と代入とか・・・
//helper functions: void init_day(Date& dd, int y, int m, int d) { // check that (y,m,d) is a valid date // if it is, use it to initialize dd } void add_day(Date& dd, int n) { // increase dd by n days }
9.4.2 Member functions and constructors
- 初期化関数は使い方を間違えればあまり役に立たない
- たとえば強制化、とは言ってもあくまでも規約だけなので、「ついうっかり」初期化を忘れたり、別の誰かがadd_dayを呼ぶのをサボったり、そもそも知らなかったりとかしたらアウト
- 大規模な開発、、、どころか小規模ですら訓練されていないプログラマならやりうる。というか自分で決めたはずの規約ですら平気で破るのが人間
- 偶然に動くコードの量産(コワイ)
- というわけで、そもそもそんなことが出来ないようにしてしまおう(あるいは困難にしてしまおう)というのがアイディア
-
- 小姑な言語ってのはこういうメリットがある。と、昔レガシーなperlで一文字違いの変数名に気がつけずに痛い目にあった僕が言っております
-
- で、メンバ関数(隠蔽はまだ)
- コンストラクタの話。これで初期化の忘れようが無い.デフォルトコンストラクタの無いクラスで引数忘れたらエラーがでる
- vervoseスタイル Date christmas = Date(1976, 12, 24); とか、タイピングが好きとか言うんでなきゃすぐに飽きるさ、とか言ってるMr.S。じゃあいったい何故このスタイルを・・・・
- やっぱり(ry
9.4.3 Keep detail private
やっときました隠蔽。世間ではwikileaksとか何でも公開する方向に向かっているようですが・・・
- やはり隠蔽は重要です。今のままではまだ間違った使い方が「できて」しまいます
- つまりは y,m,dなどの、内側の実装を直接 intとして触れてしまう、ということ
class Date { int y,m,d; public: Date(int y, int m, int d); void add_date(int n); int month() { return m; } int day() { return d; } int year() { return y; } };
-
- しれっとアクセサまで・・・
- 出入り口を制限して、その出入り口でのチェックを厳重にすることで、初めてセキュリティを守ることができるわけですね。
-
- LL言語の世界では「自分の足を撃ちぬく自由を!」という意見もあるけれど、やはり「絶対にここには間違いが無い」とassureできる構造というのは重要な気がします。問題はこれがどこまで行ってもバランス問題でしかなく、「その構造のために反って工期が延びる」場合がある、という落とし穴なのですよね
-
- 毎回明示的にチェックする!とか、大丈夫!と思い込んでこれらをスキップすることも出来るけれど、そうしたプログラムはプロの仕事じゃあない。
- 正規の値であることを決定する規則を普遍条件(invariant)という。
- 要するにassert()の中に書く条件みたいなもの
- Dateオブジェクトの普遍条件は厳密に得るのは結構難しい。閏だとかタイムゾーンだとか、、
- しかし単純な使い方ならOK
- もし、普遍条件がどうやっても見つからないとしたら、それはたんなるベタデータなので structで纏めるのみにした方が良さそうです
9.4.4 Defining member functions
- I/F設計者とユーザーの立場からDateを見てきましたが、、、
- 遅かれ早かれメンバ関数を「実装」しなければいけません
- 書き直したコード。public I/Fを先に書くらしい。
- それならstructを使えば・・・っていうツッコミ。正直この当たりは
設計ミスじゃないの
- それならstructを使えば・・・っていうツッコミ。正直この当たりは
// simple Date (some people prefer implementation details last) public: Date(int y, int m, int d); // constructor: check for valid date and initialize void add_day(int n); // increase the Date by n days int month(); // ... private: int y,m,d; // year, month, day
- public I/Fを先に書くのは、ほとんどの人がそちらを利用するから
- まあ確かに普通は実装詳細見たりしないよねーとか
- 勉強にはなるんだけどねーとか書いてる
- コンパイラはどっちでもいいと思ってる。好きにしな、って感じか
- メンバをクラス定義外で書くならどのクラスのメンバなのかを :: 使って明示する必要があるって話
- 初期化子を使わないと、まずデフォルトコンストラクタで初期化されてしまってから代入になってしまうとか、間違えて初期化前の値を使う危険がある、とかいうよくある話。
- 参照の話は書いてない・・・
- メンバ関数を直接クラス定義の中にも書けるよー、とかいう話。
- クラススコープの話。前方参照する必要は無いよーとかとか
- あ、ここでもう書いちゃうんだ、、、 クラス定義内に関数を書くと、、、
- 可能ならインライン化されます
- クラスを変更するたびにインクルードしているモジュール全ての再コンパイルが必要になります
- まあそりゃそうだね
- 5行以上の関数ならインライン化する意味は無いから書くな、とか
- Mr.Sは式が1,2個以上になったらまずクラス内には書かないそうです
9.4.5 Referring to the current object
- thisを説明スンのかと思ったら、あくまで"(A class member function) has an implicit argument which it uses to identify the object for which it is called.(クラスメンバ関数は呼ばれたのがどのオブジェクトか判別できるように暗黙の引数を持っています)"とか濁してますw
- なんとなく「わかってる人」向けの説明に思えるのですが・・・初めて聞く人はむしろ、「各オブジェクト毎にそれぞれメソッドがあってね」というプロトタイプ型の理解の方が楽だと思うんですが。この説明はクラスベースシステムに慣れすぎてる人ならでは、な気が・・・
- See section17.10 for more uses of *this* implicit argument.とか書いてますがさりげないギャグですねわかります
- pythonやperlだとこの辺の泥くさI/Fが丸見えになっていて奇妙かもです
9.4.6 Reporting errors
- 入り口でチェックはわかったけど、エラーだったらどうすんのよ?という話
- 普通に例外throwだよね。と
- いい習慣だなーと思ったのはcheck()とコンストラクタを分けてるところ。確かにクラス構築とvalidationは全く別だものね。
- ここでは Date 内に定義した class Invalid {}; を投げてる。受ける側は Date::Invalid受け