系統中的 object 應該只跟它很有關係的 object 互動,不要讓太多 class 綁在一起,免得動一個就動到一大堆。動 A 會動到 B 往往是 bug 的來源……
設計時要注意物件所互動的 class 有哪些以及是如何互動的。
針對 interface 寫 code,不針對 implement 寫 code
想使用某一堆類似的 class 時,應該是讓這些 class 有個 interface 並透過 interface 使用 class,而不是針對特定某個 class 寫 code。如果針對 interface 寫 code,當 class 又增加時可以運用多型在一開始生新的 class 的 object,之後使用該 object 的 code 都可以不用改。
多用合成,少用繼承
「合成」像是把東西組起來,是「有」,has-a。「繼承」則是「是」,is-a。
這我還沒有很懂為什麼……只想到一個爛例子,如果 class A 需要某個功能,例如 parse json,假設已經有一個 json parser 的 class,那麼應該是讓 class A 以合成的方式使用 json parser,而不是去繼承 json parser。這個例子很明顯,用繼承會讓 class A 的語意變得很奇怪,不過不是每種狀況都那麼明顯……
list 繼承 __list_imp,所以 __link_nodes_at_front() 的 base::__end_ 是 __list_imp 的 __end_。從上面四個 function 看起來,__end_.__prev_ 指到 list 的最後一個 node,__end_.__next_ 指向 list 第一個 node。也就是說,__end_ 是用來記錄 list 的第一個及最後一個 node。
d-pointer 是個 design pattern,也叫 Opaque pointer 或 Pimpl idiom。它分開給外面看的公開 class interface 跟實作的 class。做法是在公開 class 放個實作 class 的 pointer,公開 class 各 function 只是 call implementation class 的 function。公開 class 中跟實作相關的 member 只有那個 pointer,所以 size 會固定是 pointer 的 size。老覺得在哪看過類似的東西……
Qt 用 d-pointer 主要是為了保持 binary compatibility。
Binary Compatibility
有 binary compatibility 表示 application 在以舊版 library compile 後,新版 library binary 可以直接取代舊版 library 而不需要重新 compile application。要達到 binary compatibility 就不能改變已經公開出去的 C++ class 的 size 或 memory layout,即不能增加 member 或者更改 function 跟 member 的順序。如果改了,application 認定的 library class size 或 memory layout 跟新版 library 不同,會把 memory 踩爛掉。
Qt 使用 d-pointer 達到不修改既有公開 class 還能增加 feature 或修改實作細節。
voidQStandardItemPrivate::setChild(int row, int column, QStandardItem *item, bool emitChanged) { Q_Q(QStandardItem); ... if (rows <= row) q->setRowCount(row + 1); ... }
透過 q_func() 拿到的 q 是轉型成 QStandardItem* 的 pointer。
這個例子裡,因為 QStandardItem 跟 QStandardItemPrivate 沒有繼承別人,它們自己分別記錄了 d_ptr 跟 q_ptr,所以 d_func() 跟 q_func() 有沒有轉型根本沒差。但其他繼承 QObjectPrivate 的 class,q_ptr 是從 QObjectData 來的,type 是 QObject*,如果要直接使用 sub class 的 member 就得轉型,d_ptr 同理。除非用 virtual function 做多型,不過這個狀況用 virtual function 很怪,因為這樣 base class QObject 得有所有 sub class 會用到的 interface,這邊顯然不是這個意思,用 virtual function 不合理。
要執行一隻程式須將它的 instruction 跟 data load 到 memory。現代 OS 結合 virtual memory 以及 memory management 的 paging 機制來 load process。
virtual memory 將 process 認知的 memory space 跟 physical memory 分離,藉此達到 process 不一定要在它認定的 physical memory address 上執行。如果實際有的記憶體比較少,virtual memory 也可以在記憶體不足的情況下讓 process 以為它有這麼多 memory 可用。
每個 process 有自己的 virtual memory space,virtual memory space 的大小由平台的定址 bit 數決定,例如 32 bit 的平台可定址的 address 就從 0 到 2^32 - 1,總大小為 4 GB。process 在 32 bit 系統中的 virtual memory space 大小是 4GB,但不是 4GB 都可以為 process 使用,process 只能用 OS 分配給它的空間,亂用的話會被強制結束。
paging 簡單來說是將 memory space 分成固定 size 的 page,現在 OS 大多使用 4K size 的 page。virtual memory space 跟 physical memory space 都以 page 切分,MMU(Memory Management Unit)負責 virtual page 與 physical page 的 mapping。virtual page 不存在 physical memory 而該 virtual page 又要被使用時會發生 page fault,此時 OS 負責將 virtual page 從 disk 中讀到 memory 並建立 virtual page 與 physical page 之間的 mapping。
結合 virtual memory 跟 paging 的 load process 步驟:
建立這個 process 的 virtual memory space,實際上是建立 virtual page 對應到 physical page 所需的 data structure。
由 executable file header 的資訊建立 virtual space 與 executable file 的關係,也就是 process 的 virtual memory space 哪一塊對應到 executable file 的哪一塊。
將 PC register 設到執行入口,process 開始執行。
process 執行到的程式或需要的 data 沒有 load 進 physical memory,發生 page fault。OS 依照 virtual space 與 executable file 的對應關係知道要從 disk load 哪個 page 到 physical memory,load 進 physical memory 後建立 virtual apge 與該 physical page 的關係,這段即一般講 load 所指的動作。控制權還給 process 繼續跑。