Effective C++ 第三十一條 Minimize compilation dependencies between fi

將文件間的編譯依存關(guān)系降至最低
新手寫C++的時(shí)候,可能會(huì)喜歡把所有函數(shù)放在一個(gè)文件下,顯得功能又大又全,又或者是在寫類的時(shí)候弄很多成員函數(shù),其實(shí)這樣對于經(jīng)常修改的代碼來說不好?,F(xiàn)在給出單文件編譯和多文件編譯的例子,分別進(jìn)行對比。
單文件:
多文件:
如果采用單文件,fun1 和 fun2 即使毫無關(guān)系也被聚合在一起了,因?yàn)橐坏?fun1 修改,整個(gè) xx.h 都要重新編譯,即使 fun2 沒有修改也和 fun1 毫無關(guān)系。在大型項(xiàng)目下,會(huì)產(chǎn)生冗余的編譯時(shí)間,浪費(fèi)時(shí)間。如果采取多文件,fun1 修改只需要重新編譯 f1.c 就可以了,關(guān)于 fun2 的內(nèi)容無需修改。所以對于經(jīng)常重新編譯的 main.cpp 來說,不放入函數(shù)是非常有必要的。
但是采取多文件系統(tǒng)也會(huì)有一些新手難以解決的問題
這里無法編譯通過,因?yàn)?a 的類型A是什么,編譯器不知道。所以必須包括 A.h 這個(gè)頭文件,這就將B.h 和 A.h 強(qiáng)行綁定了,因?yàn)?B 在創(chuàng)建的時(shí)候編譯器必須知道類型 B 所占空間,那么就必須得到 A 的定義式才可以。如果所有文件都是這樣相互聯(lián)結(jié),那么許多項(xiàng)目可能為了使用一個(gè)很小的功能就要包含非常多的頭文件,而且編譯還要帶上.c文件。這就是巨大的災(zāi)難。
有一個(gè)很好的辦法就是用指針代替對象
這樣就不需要知道 A 到底多大,只需要引入一個(gè) A.h 頭文件就可以編譯通過,當(dāng)然如果你需要使用 A 的功能那么還是需要編譯的時(shí)候帶上 A.c 文件進(jìn)行編譯。但是如果我們只進(jìn)行關(guān)于 val 的操作而不進(jìn)行關(guān)于 A 的操作,哪怕編譯的時(shí)候不加 A.c 文件也無關(guān)緊要,而且這樣做更加節(jié)省空間。假如用的不是指針而是對象,我們在B中是要包含A的,一般來說開銷肯定大于一個(gè)指針的開銷(8 Bytes)。這種方法叫做 handles classes。在之前的章節(jié)有所介紹。
還有一種辦法叫做 interface classes。就是采用一個(gè)抽象類(java的說法)創(chuàng)建接口,不具體實(shí)現(xiàn)
這樣的 person 無法使用其 object,只能使用 reference。這種好處就是使得接口和實(shí)現(xiàn)分離,降低了編譯的耦合度。person 可以派生出多了類,這些類需要重載 op 函數(shù)。如果 op 函數(shù)實(shí)現(xiàn)細(xì)節(jié)修改了,person.c 文件是不需要重新編譯的,除非 person 的接口都改變了。
采用 handles classes 和 interface classes 也會(huì)有缺陷,在 handles classes 上成員函數(shù)需要通過 implementation pointer獲取對象數(shù)據(jù),這就造成了一次多的間接訪問,還要進(jìn)行動(dòng)態(tài)分配內(nèi)存??赡軙?huì)出現(xiàn)分配失敗的錯(cuò)誤,即使分配成功也要承受額外開銷。如果采取 interface classes,每一個(gè) 派生類需要為 interface class 在原本的內(nèi)存開銷上增加一個(gè) vptr (virtual table pointer),如果有其他 virtual 函數(shù)還要多增加其他的虛函數(shù)指針。造成內(nèi)存開銷變大。(條款 7 有所介紹)
總結(jié):
支持編譯依存性最小的構(gòu)想是,依托于聲明式,不依托于定義式,兩個(gè)手段是 handles classes 和 interface classes。
頭文件中應(yīng)該完全僅有聲明式